Source code for mdcraft.fit.fourier

r"""
Fourier series models
=====================
.. moduleauthor:: Benjamin Ye <GitHub: @bbye98>

The Fourier series is a sum of sine and cosine functions that describes
a periodic signal, and can be represented in trigonometric form as

.. math::

   y=a_0+\sum_{k=1}^na_k\cos{(k\omega x)}+b_k\sin{(k\omega x)}

where :math:`a_0` models a constant (intercept) term in the data and is
associated with the :math:`k=0` cosine term, :math:`\omega` is the
fundamental frequency of the signal, and :math:`n` is the number of
terms (harmonics) in the series.

This module provides the general Fourier series model above for any
number of terms :math:`k`, as well as convenience functions for Fourier
series models with :math:`1\leq k\leq 8` analogous to MATLAB's
:code:`fourier1`, :code:`fourier2`, etc.
"""

import numpy as np


[docs] def fourier(x: np.ndarray, omega: float, a0: float, *args: float) -> np.ndarray: r""" General Fourier series model. .. math:: y=a_0+\sum_{k=1}^na_i\cos{(k\omega x)}+b_i\sin{(k\omega x)} Parameters ---------- x : `numpy.ndarray` :math:`x`-values. omega : `float` Fundamental frequency :math:`\omega` of the signal. a0 : `float` Constant (intercept) term :math:`a_0` for the :math:`k=0` cosine term. *args : `float` Fitting parameters for the Fourier series term(s), ordered as :math:`a_1,\,b_1,\,a_2,\,b_2,\ldots,\,a_n,\,b_n`, where :math:`n` is the number of terms in the model. As such, the number of variable positional arguments must be even. Returns ------- fit : `numpy.ndarray` Fitted :math:`y`-values. Examples -------- Generate :math:`x`- and :math:`y`-values (with error), and then use :meth:`scipy.optimize.curve_fit` to fit coefficients for a one-term Fourier series model. >>> from scipy import optimize >>> rng = np.random.default_rng() >>> x = np.linspace(0, 5, 20) >>> err = (2 * rng.random(x.shape) - 1) / 10 >>> y = 1 + 2 * np.cos(x / 2) + 3 * np.sin(x / 2) + err >>> pk, _ = optimize.curve_fit( lambda x, omega, a0, a1, b1: fourier(x, omega, a0, a1, b1), x, y ) >>> pk array([0.51185734, 1.15090712, 1.87471839, 2.87117784]) Evaluate the fitted :math:`y`-values using the coefficients. >>> fourier(x, *pk) array([3.0256255 , 3.39422104, 3.72217562, 4.00354785, 4.23324026, 4.40709163, 4.52195238, 4.57574164, 4.56748494, 4.49733187, 4.36655334, 4.1775186 , 3.9336523 , 3.63937245, 3.30001035, 2.92171405, 2.51133696, 2.07631367, 1.62452525, 1.16415655]) """ n = len(args) assert ( n >= 2 and n % 2 == 0 ), "Number of fitting parameters must be greater than 2 and even." kwx = np.arange(1, n // 2 + 1)[:, None] * omega * x return a0 + args[::2] @ np.cos(kwx) + args[1::2] @ np.sin(kwx)
[docs] def fourier1( x: np.ndarray, a0: float, a1: float, b1: float, omega: float ) -> np.ndarray: r""" Convenience function for the :code:`fourier1` model from MATLAB. .. math:: y=a_0+a_1\cos{(\omega x)}+b_1\sin{(\omega x)} Parameters ---------- x : `numpy.ndarray` :math:`x`-values. a0 : `float` Constant (intercept) term :math:`a_0` for the :math:`k=0` cosine term. a1 : `float` Coefficient :math:`a_1` for the first :math:`\cos` term. b1 : `float` Coefficient :math:`b_1` for the first :math:`\sin` term. omega : `float` Fundamental frequency :math:`\omega` of the signal. Returns ------- fit : `numpy.ndarray` Fitted :math:`y`-values. """ return fourier(x, omega, a0, a1, b1)
[docs] def fourier2( x: np.ndarray, a0: float, a1: float, b1: float, a2: float, b2: float, omega: float ) -> np.ndarray: r""" Convenience function for the :code:`fourier2` model from MATLAB. .. math:: y=a_0+a_1\cos{(\omega x)}+b_1\sin{(\omega x)} +a_2\cos{(2\omega x)}+b_2\sin{(2\omega x)} Parameters ---------- x : `numpy.ndarray` :math:`x`-values. a0 : `float` Constant (intercept) term :math:`a_0` for the :math:`k=0` cosine term. a1 : `float` Coefficient :math:`a_1` for the first :math:`\cos` term. b1 : `float` Coefficient :math:`b_1` for the first :math:`\sin` term. a2 : `float` Coefficient :math:`a_2` for the second :math:`\cos` term. b2 : `float` Coefficient :math:`b_2` for the second :math:`\sin` term. omega : `float` Fundamental frequency :math:`\omega` of the signal. Returns ------- fit : `numpy.ndarray` Fitted :math:`y`-values. """ return fourier(x, omega, a0, a1, b1, a2, b2)
[docs] def fourier3( x: np.ndarray, a0: float, a1: float, b1: float, a2: float, b2: float, a3: float, b3: float, omega: float, ) -> np.ndarray: r""" Convenience function for the :code:`fourier3` model from MATLAB. .. math:: y=a_0+a_1\cos{(\omega x)}+b_1\sin{(\omega x)} +a_2\cos{(2\omega x)}+b_2\sin{(2\omega x)} +a_3\cos{(3\omega x)}+b_3\sin{(3\omega x)} Parameters ---------- x : `numpy.ndarray` :math:`x`-values. a0 : `float` Constant (intercept) term :math:`a_0` for the :math:`k=0` cosine term. a1 : `float` Coefficient :math:`a_1` for the first :math:`\cos` term. b1 : `float` Coefficient :math:`b_1` for the first :math:`\sin` term. a2 : `float` Coefficient :math:`a_2` for the second :math:`\cos` term. b2 : `float` Coefficient :math:`b_2` for the second :math:`\sin` term. a3 : `float` Coefficient :math:`a_3` for the third :math:`\cos` term. b3 : `float` Coefficient :math:`b_3` for the third :math:`\sin` term. omega : `float` Fundamental frequency :math:`\omega` of the signal. Returns ------- fit : `numpy.ndarray` Fitted :math:`y`-values. """ return fourier(x, omega, a0, a1, b1, a2, b2, a3, b3)
[docs] def fourier4( x: np.ndarray, a0: float, a1: float, b1: float, a2: float, b2: float, a3: float, b3: float, a4: float, b4: float, omega: float, ) -> np.ndarray: r""" Convenience function for the :code:`fourier4` model from MATLAB. .. math:: y=a_0+a_1\cos{(\omega x)}+b_1\sin{(\omega x)} +a_2\cos{(2\omega x)}+b_2\sin{(2\omega x)} +a_3\cos{(3\omega x)}+b_3\sin{(3\omega x)} +a_4\cos{(4\omega x)}+b_4\sin{(4\omega x)} Parameters ---------- x : `numpy.ndarray` :math:`x`-values. a0 : `float` Constant (intercept) term :math:`a_0` for the :math:`k=0` cosine term. a1 : `float` Coefficient :math:`a_1` for the first :math:`\cos` term. b1 : `float` Coefficient :math:`b_1` for the first :math:`\sin` term. a2 : `float` Coefficient :math:`a_2` for the second :math:`\cos` term. b2 : `float` Coefficient :math:`b_2` for the second :math:`\sin` term. a3 : `float` Coefficient :math:`a_3` for the third :math:`\cos` term. b3 : `float` Coefficient :math:`b_3` for the third :math:`\sin` term. a4 : `float` Coefficient :math:`a_4` for the fourth :math:`\cos` term. b4 : `float` Coefficient :math:`b_4` for the fourth :math:`\sin` term. omega : `float` Fundamental frequency :math:`\omega` of the signal. Returns ------- fit : `numpy.ndarray` Fitted :math:`y`-values. """ return fourier(x, omega, a0, a1, b1, a2, b2, a3, b3, a4, b4)
[docs] def fourier5( x: np.ndarray, a0: float, a1: float, b1: float, a2: float, b2: float, a3: float, b3: float, a4: float, b4: float, a5: float, b5: float, omega: float, ) -> np.ndarray: r""" Convenience function for the :code:`fourier5` model from MATLAB. .. math:: y=a_0+a_1\cos{(\omega x)}+b_1\sin{(\omega x)} +a_2\cos{(2\omega x)}+b_2\sin{(2\omega x)}+\cdots +a_5\cos{(5\omega x)}+b_5\sin{(5\omega x)} Parameters ---------- x : `numpy.ndarray` :math:`x`-values. a0 : `float` Constant (intercept) term :math:`a_0` for the :math:`k=0` cosine term. a1 : `float` Coefficient :math:`a_1` for the first :math:`\cos` term. b1 : `float` Coefficient :math:`b_1` for the first :math:`\sin` term. a2 : `float` Coefficient :math:`a_2` for the second :math:`\cos` term. b2 : `float` Coefficient :math:`b_2` for the second :math:`\sin` term. a3 : `float` Coefficient :math:`a_3` for the third :math:`\cos` term. b3 : `float` Coefficient :math:`b_3` for the third :math:`\sin` term. a4 : `float` Coefficient :math:`a_4` for the fourth :math:`\cos` term. b4 : `float` Coefficient :math:`b_4` for the fourth :math:`\sin` term. a5 : `float` Coefficient :math:`a_5` for the fifth :math:`\cos` term. b5 : `float` Coefficient :math:`b_5` for the fifth :math:`\sin` term. omega : `float` Fundamental frequency :math:`\omega` of the signal. Returns ------- fit : `numpy.ndarray` Fitted :math:`y`-values. """ return fourier(x, omega, a0, a1, b1, a2, b2, a3, b3, a4, b4, a5, b5)
[docs] def fourier6( x: np.ndarray, a0: float, a1: float, b1: float, a2: float, b2: float, a3: float, b3: float, a4: float, b4: float, a5: float, b5: float, a6: float, b6: float, omega: float, ) -> np.ndarray: r""" Convenience function for the :code:`fourier6` model from MATLAB. .. math:: y=a_0+a_1\cos{(\omega x)}+b_1\sin{(\omega x)} +a_2\cos{(2\omega x)}+b_2\sin{(2\omega x)}+\cdots +a_6\cos{(6\omega x)}+b_6\sin{(6\omega x)} Parameters ---------- x : `numpy.ndarray` :math:`x`-values. a0 : `float` Constant (intercept) term :math:`a_0` for the :math:`k=0` cosine term. a1 : `float` Coefficient :math:`a_1` for the first :math:`\cos` term. b1 : `float` Coefficient :math:`b_1` for the first :math:`\sin` term. a2 : `float` Coefficient :math:`a_2` for the second :math:`\cos` term. b2 : `float` Coefficient :math:`b_2` for the second :math:`\sin` term. a3 : `float` Coefficient :math:`a_3` for the third :math:`\cos` term. b3 : `float` Coefficient :math:`b_3` for the third :math:`\sin` term. a4 : `float` Coefficient :math:`a_4` for the fourth :math:`\cos` term. b4 : `float` Coefficient :math:`b_4` for the fourth :math:`\sin` term. a5 : `float` Coefficient :math:`a_5` for the fifth :math:`\cos` term. b5 : `float` Coefficient :math:`b_5` for the fifth :math:`\sin` term. a6 : `float` Coefficient :math:`a_6` for the sixth :math:`\cos` term. b6 : `float` Coefficient :math:`b_6` for the sixth :math:`\sin` term. omega : `float` Fundamental frequency :math:`\omega` of the signal. Returns ------- fit : `numpy.ndarray` Fitted :math:`y`-values. """ return fourier(x, omega, a0, a1, b1, a2, b2, a3, b3, a4, b4, a5, b5, a6, b6)
[docs] def fourier7( x: np.ndarray, a0: float, a1: float, b1: float, a2: float, b2: float, a3: float, b3: float, a4: float, b4: float, a5: float, b5: float, a6: float, b6: float, a7: float, b7: float, omega: float, ) -> np.ndarray: r""" Convenience function for the :code:`fourier7` model from MATLAB. .. math:: y=a_0+a_1\cos{(\omega x)}+b_1\sin{(\omega x)} +a_2\cos{(2\omega x)}+b_2\sin{(2\omega x)}+\cdots +a_7\cos{(7\omega x)}+b_7\sin{(7\omega x)} Parameters ---------- x : `numpy.ndarray` :math:`x`-values. a0 : `float` Constant (intercept) term :math:`a_0` for the :math:`k=0` cosine term. a1 : `float` Coefficient :math:`a_1` for the first :math:`\cos` term. b1 : `float` Coefficient :math:`b_1` for the first :math:`\sin` term. a2 : `float` Coefficient :math:`a_2` for the second :math:`\cos` term. b2 : `float` Coefficient :math:`b_2` for the second :math:`\sin` term. a3 : `float` Coefficient :math:`a_3` for the third :math:`\cos` term. b3 : `float` Coefficient :math:`b_3` for the third :math:`\sin` term. a4 : `float` Coefficient :math:`a_4` for the fourth :math:`\cos` term. b4 : `float` Coefficient :math:`b_4` for the fourth :math:`\sin` term. a5 : `float` Coefficient :math:`a_5` for the fifth :math:`\cos` term. b5 : `float` Coefficient :math:`b_5` for the fifth :math:`\sin` term. a6 : `float` Coefficient :math:`a_6` for the sixth :math:`\cos` term. b6 : `float` Coefficient :math:`b_6` for the sixth :math:`\sin` term. a7 : `float` Coefficient :math:`a_7` for the seventh :math:`\cos` term. b7 : `float` Coefficient :math:`b_7` for the seventh :math:`\sin` term. omega : `float` Fundamental frequency :math:`\omega` of the signal. Returns ------- fit : `numpy.ndarray` Fitted :math:`y`-values. """ return fourier(x, omega, a0, a1, b1, a2, b2, a3, b3, a4, b4, a5, b5, a6, b6, a7, b7)
[docs] def fourier8( x: np.ndarray, a0: float, a1: float, b1: float, a2: float, b2: float, a3: float, b3: float, a4: float, b4: float, a5: float, b5: float, a6: float, b6: float, a7: float, b7: float, a8: float, b8: float, omega: float, ) -> np.ndarray: r""" Convenience function for the :code:`fourier8` model from MATLAB. .. math:: y=a_0+a_1\cos{(\omega x)}+b_1\sin{(\omega x)} +a_2\cos{(2\omega x)}+b_2\sin{(2\omega x)}+\cdots +a_8\cos{(8\omega x)}+b_8\sin{(8\omega x)} Parameters ---------- x : `numpy.ndarray` :math:`x`-values. a0 : `float` Constant (intercept) term :math:`a_0` for the :math:`k=0` cosine term. a1 : `float` Coefficient :math:`a_1` for the first :math:`\cos` term. b1 : `float` Coefficient :math:`b_1` for the first :math:`\sin` term. a2 : `float` Coefficient :math:`a_2` for the second :math:`\cos` term. b2 : `float` Coefficient :math:`b_2` for the second :math:`\sin` term. a3 : `float` Coefficient :math:`a_3` for the third :math:`\cos` term. b3 : `float` Coefficient :math:`b_3` for the third :math:`\sin` term. a4 : `float` Coefficient :math:`a_4` for the fourth :math:`\cos` term. b4 : `float` Coefficient :math:`b_4` for the fourth :math:`\sin` term. a5 : `float` Coefficient :math:`a_5` for the fifth :math:`\cos` term. b5 : `float` Coefficient :math:`b_5` for the fifth :math:`\sin` term. a6 : `float` Coefficient :math:`a_6` for the sixth :math:`\cos` term. b6 : `float` Coefficient :math:`b_6` for the sixth :math:`\sin` term. a7 : `float` Coefficient :math:`a_7` for the seventh :math:`\cos` term. b7 : `float` Coefficient :math:`b_7` for the seventh :math:`\sin` term. a8 : `float` Coefficient :math:`a_8` for the eigth :math:`\cos` term. b8 : `float` Coefficient :math:`b_8` for the eigth :math:`\sin` term. omega : `float` Fundamental frequency :math:`\omega` of the signal. Returns ------- fit : `numpy.ndarray` Fitted :math:`y`-values. """ return fourier( x, omega, a0, a1, b1, a2, b2, a3, b3, a4, b4, a5, b5, a6, b6, a7, b7, a8, b8 )