2

Ok, so I have been trying to code a "naive" method to calculate the coefficients for a standard Fourier Series in complex form. I am getting very close, I think, but there are some odd behaviors. This may be more of a math question than programming one, but I already asked on math.stackexchange and got zero answers. Here is my working code:

import matplotlib.pyplot as plt
import numpy as np


def coefficients(fn, dx, m, L):
    """
    Calculate the complex form fourier series coefficients for the first M
    waves.

    :param fn: function to sample
    :param dx: sampling frequency
    :param m: number of waves to compute
    :param L: We are solving on the interval [-L, L]
    :return: an array containing M Fourier coefficients c_m
    """

    N = 2*L / dx
    coeffs = np.zeros(m, dtype=np.complex_)
    xk = np.arange(-L, L + dx, dx)

    # Calculate the coefficients for each wave
    for mi in range(m):
        coeffs[mi] = 1/N * sum(fn(xk)*np.exp(-1j * mi * np.pi * xk / L))

    return coeffs


def fourier_graph(range, L, c_coef, function=None, plot=True, err_plot=False):
    """
    Given a range to plot and an array of complex fourier series coefficients,
    this function plots the representation.


    :param range: the x-axis values to plot
    :param c_coef: the complex fourier coefficients, calculated by coefficients()
    :param plot: Default True. Plot the fourier representation
    :param function: For calculating relative error, provide function definition
    :param err_plot: relative error plotted. requires a function to compare solution to
    :return: the fourier series values for the given range
    """
    # Number of coefficients to sum over
    w = len(c_coef)

    # Initialize solution array
    s = np.zeros(len(range))
    for i, ix in enumerate(range):
        for iw in np.arange(w):
            s[i] += c_coef[iw] * np.exp(1j * iw * np.pi * ix / L)

    # If a plot is desired:
    if plot:
        plt.suptitle("Fourier Series Plot")
        plt.xlabel(r"$t$")
        plt.ylabel(r"$f(x)$")
        plt.plot(range, s, label="Fourier Series")

        if err_plot:
            plt.plot(range, function(range), label="Actual Solution")
            plt.legend()

        plt.show()

    # If error plot is desired:
    if err_plot:
        err = abs(function(range) - s) / function(range)
        plt.suptitle("Plot of Relative Error")
        plt.xlabel("Steps")
        plt.ylabel("Relative Error")
        plt.plot(range, err)
        plt.show()

    return s


if __name__ == '__main__':

    # Assuming the interval [-l, l] apply discrete fourier transform:

    # number of waves to sum
    wvs = 50

    # step size for calculating c_m coefficients (trap rule)
    deltax = .025 * np.pi

    # length of interval for Fourier Series is 2*l
    l = 2 * np.pi

    c_m = coefficients(np.exp, deltax, wvs, l)

    # The x range we would like to interpolate function values
    x = np.arange(-l, l, .01)
    sol = fourier_graph(x, l, c_m, np.exp, err_plot=True)

Now, there is a factor of 2/N multiplying each coefficient. However, I have a derivation of this sum in my professor's typed notes that does not include this factor of 2/N. When I derived the form myself, I arrived at a formula with a factor of 1/N that did not cancel no matter what tricks I tried. I asked over at math.stackexchange what was going on, but got no answers.

What I did notice is that adding the 1/N decreased the difference between the actual solution and the fourier series by a massive amount, but it's still not right. so I tried 2/N and got even better results. I am really trying to figure this out so I can write a nice, clean algorithm for basic fourier series before I try to learn about Fast Fourier Transforms.

So what am I doing wrong here?

rocksNwaves
  • 5,331
  • 4
  • 38
  • 77
  • If it stays unanswered here, you might try https://codereview.stackexchange.com/ – JohanC Nov 11 '19 at 20:58
  • 1
    Maybe some ideas [here](https://codereview.stackexchange.com/search?q=%5Bpython%5D+fourier)? – JohanC Nov 11 '19 at 21:02
  • I will take a look, thank you. – rocksNwaves Nov 11 '19 at 21:03
  • Not the same issue necessarily, but I was also trying to implement the naive DFT algorithm, and was having some confusion about this or that. I [posted over in Signal Processing SE](https://dsp.stackexchange.com/a/60382/33763) and got a pretty good general explanation that was very helpful. – Him Nov 11 '19 at 23:15
  • @Scott, Thank you. I wish I knew how this sort of thing is applied to real world problems. The "signals" language confuses me, since I'm used to looking at this from an abstract math point of view. The goal is to learn to apply it one day, though. I think your post may be helpful for that. – rocksNwaves Nov 11 '19 at 23:19
  • Numerical integration typically isn't a good idea for computing Fourier coefficients. You have to be very careful about the extent of error in your integration... Ideally, you want to figure out some way of getting FFT to do the heavy-lifting for you. Also, you seem to be trying to compute the Fourier series of `exp(x)`, but that's not a square-integrable function, so I don't see how your Fourier series will converge... Maybe you should try taking the Fourier series of `exp(-|x|)`, instead? – Praveen Nov 12 '19 at 02:18
  • @Praveen I'm trying a naive way first (I want to learn from scratch). I am conceptually aware of better methods, but I dont want to jump the gun too much. After I master this simple way, I'll move on to FFT, which I've only begun reading about. That being said, I havent heard of square integrable before. I'll try your suggestion. – rocksNwaves Nov 12 '19 at 03:10

1 Answers1

2

assuming c_n is given by A_n as in mathworld

idem c_n = 1/T \int_{-T/2}^{T/2}f(x)e^{-2ipinx/T}dx

we can compute (trivially) the coeffs c_n analytically (which is a good way to compare to your trapezoidal integral)

k = (1-2in)/2
c_n = 1/(4*pi*k)*(e^{2pik} - e^{-2pik})

So your coeffs are likely to be properly computed (the both wrong curves look alike)

Now notice that when you reconstitue f, you add the coeff c_0 up to c_m

But the reconstruction should occur with c_{-m} to c_m

So you are missing half of the coeffs.

Below a fix with your adaptated coefficients function and the theoretical coeffs

import matplotlib.pyplot as plt
import numpy as np


def coefficients(fn, dx, m, L):
    """
    Calculate the complex form fourier series coefficients for the first M
    waves.

    :param fn: function to sample
    :param dx: sampling frequency
    :param m: number of waves to compute
    :param L: We are solving on the interval [-L, L]
    :return: an array containing M Fourier coefficients c_m
    """

    N = 2*L / dx
    coeffs = np.zeros(m, dtype=np.complex_)
    xk = np.arange(-L, L + dx, dx)

    # Calculate the coefficients for each wave
    for mi in range(m):
        n = mi - m/2
        coeffs[mi] = 1/N * sum(fn(xk)*np.exp(-1j * n * np.pi * xk / L))

    return coeffs


def fourier_graph(range, L, c_coef, ref, function=None, plot=True, err_plot=False):
    """
    Given a range to plot and an array of complex fourier series coefficients,
    this function plots the representation.


    :param range: the x-axis values to plot
    :param c_coef: the complex fourier coefficients, calculated by coefficients()
    :param plot: Default True. Plot the fourier representation
    :param function: For calculating relative error, provide function definition
    :param err_plot: relative error plotted. requires a function to compare solution to
    :return: the fourier series values for the given range
    """
    # Number of coefficients to sum over
    w = len(c_coef)

    # Initialize solution array
    s = np.zeros(len(range), dtype=complex)
    t = np.zeros(len(range), dtype=complex)

    for i, ix in enumerate(range):
        for iw in np.arange(w):
            n = iw - w/2
            s[i] += c_coef[iw] * (np.exp(1j * n * ix * 2 * np.pi / L))
            t[i] += ref[iw] * (np.exp(1j * n * ix * 2 * np.pi / L))

    # If a plot is desired:
    if plot:
        plt.suptitle("Fourier Series Plot")
        plt.xlabel(r"$t$")
        plt.ylabel(r"$f(x)$")
        plt.plot(range, s, label="Fourier Series")

        plt.plot(range, t, label="expected Solution")
        plt.legend()

        if err_plot:
            plt.plot(range, function(range), label="Actual Solution")
            plt.legend()

        plt.show()

    return s

def ref_coefficients(m):
    """
    Calculate the complex form fourier series coefficients for the first M
    waves.

    :param fn: function to sample
    :param dx: sampling frequency
    :param m: number of waves to compute
    :param L: We are solving on the interval [-L, L]
    :return: an array containing M Fourier coefficients c_m
    """

    coeffs = np.zeros(m, dtype=np.complex_)

    # Calculate the coefficients for each wave
    for iw in range(m):
        n = iw - m/2
        k = (1-(1j*n)/2)
        coeffs[iw] = 1/(4*np.pi*k)* (np.exp(2*np.pi*k) - np.exp(-2*np.pi*k))
    return coeffs

if __name__ == '__main__':

    # Assuming the interval [-l, l] apply discrete fourier transform:

    # number of waves to sum
    wvs = 50

    # step size for calculating c_m coefficients (trap rule)
    deltax = .025 * np.pi

    # length of interval for Fourier Series is 2*l
    l = 2 * np.pi

    c_m = coefficients(np.exp, deltax, wvs, l)

    # The x range we would like to interpolate function values
    x = np.arange(-l, l, .01)
    ref = ref_coefficients(wvs)
    sol = fourier_graph(x, 2*l, c_m, ref, np.exp, err_plot=True)

grodzi
  • 5,633
  • 1
  • 15
  • 15
  • 1
    Thanks! It looks like I computed the right number of coefficients, but with improper index values. I see for any given number `m` of "wave" coefficients, you and I ended up calculating the same number of coefficients, but you indexed them from `-m/2` to `m/2`, where I went from `0` to `m`. Once I switched over to your correct method, I found I could drop the 2/N factor back to the correct 1/N and get the correct amplitudes. I also like that you noticed that it was easy to solve for `c_m` analytically in order to compare. I didn't think about that! – rocksNwaves Nov 12 '19 at 16:47
  • I notice that if I extend the plot, I get periodic behavior as expected, but every other period, the graph is reflected over the horizontal axis... Is that normal? – rocksNwaves Nov 12 '19 at 16:52
  • fourier is too behind so I can answer your question correctly.. Hopefully someone else should be easily able to answer your question if you post another one (probably on math though). The only symmetry I can think of is a vertical one (centered in 0) when ploting |fft(f)| but this does not answer yours – grodzi Nov 12 '19 at 16:57