Fourier Coefficients!
Contents
Fourier Coefficients!#
Recap on PDEs from last lecture#
PDE’s are more complicated than ODE’s
You need to analyze each independent variable (x, y, t, etc), determine the order, and then determine the number of boundary / initial conditions you need
Many PDE’s that are important in chemical engineering are linear and second order
\(u_t=c^2u_{xx}\)
\(u_{tt}=c^2u_{xx}\)
\(\nabla^2u=0\)
Separation of variables for linear PDEs:
Assume \(u(x,t)=X(x)T(t)\)
Plug in to PDE, separate, set equal to a constant to yield multiple ODE’s
Solve each ODE
Get general solution
Apply boundary conditions to constrain the problem
Initial conditions require a linear combination of the possible solutions with coefficients \(c_n\) that we talked about last time.
Reminder: constant temperature initial condition for heated bar#
Solving a problem with a bar that has a sin temperature profile is not very interesting. How would you make a bar like that? I have no idea.
Let’s do a more reasonable set of initial conditions \(f(x)=T_0=5\), that is, we immerse the bar in a water bath 5 K warmer than our hands, then take it out and hold it out on both sides. We don’t have time to show how to solve this equation right now (we’ll do that next class). The magic \(c_n\) that specify this are
That makes our final solution
with
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
L=1 #m
alpha=0.1 #m^2/s
lambdan=alpha*(np.pi/L)**2
T0 = 5
x = np.linspace(0, L, 200)
# First set up the figure, the axis, and the plot element we want to animate
fig, ax = plt.subplots()
plt.close()
T0 = 5*np.ones(x.shape)
ax.plot(x,T0,'--',label='Initial Temp Profile')
ax.set_xlim(( -0.5, 1.5))
ax.set_ylim((0, 6))
ax.set_xlabel('x')
ax.set_ylabel('Temperature [K]')
line, = ax.plot([], [], lw=2, label='Temperature Profile')
ax.legend()
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return (line,)
# animation function. This is called sequentially
def animate(i):
T = np.zeros(len(x))
for n in range(1,100):
if n%2==1:
cn=4*T0/np.pi/n
else:
cn=0
lambdan=alpha*(n*np.pi/L)**2
T=T+cn*np.exp(-lambdan*t[i])*np.sin(np.pi*n*x/L)
line.set_data(x, T)
return (line,)
t = np.linspace(0,3,100)
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=100, interval=100, blit=True)
# Note: below is the part which makes it work on Colab
rc('animation', html='jshtml')
anim
In summary, the initial value problem we’re faced with is:
Fourier Series#
The Fourier series is a special series approximation to a function in terms of sin and cos terms
where \(f(x)\) is an arbitrary function and \(c_n\) are the Fourier coefficients.
Because the Fourier series is in terms of sin/cos, it is periodic
This process is guaranteed to converge.
How do we find the Fourier coefficients \(c_n\)?
Special formula for sin/cos for each term:
Notice that the range is defined from \(-\pi\) to \(\pi\) - we will deal with that later.
Example: flat line \(f(x)=c\)#
We need to evaluate each of those terms above. The constant term is easy
Now let’s do the sin/cos terms
So the Fourier series for \(f(x)=c\) is just \(f(x)=c\). Easy! It was harder for the bar example because we were contrained to \(T(0,t)=T(L,t)=0\).
Example: square wave#
Let’s consider
We still need to evaluate each of those terms above.
Now for the \(a_n\) terms
So all the \(a_n\) terms are zero. One last one.
This is a little tricky. There are two possibilities for n odd and n even. If n is odd
If n is even, then \(\cos 0=\cos n\pi\) and \(b_n=0\). So the Fourier series for this function is
Look familiar? Let’s plot this!
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
k=5
x = np.linspace(-2*np.pi , 2*np.pi, 200)
# First set up the figure, the axis, and the plot element we want to animate
fig, ax = plt.subplots()
plt.close()
f = -5*(x<0)+5*(x>0)
ax.plot(x,f,'--',label='f(x)')
ax.set_xlabel('x')
ax.set_ylabel('f(x)')
ax.set_ylim((-k-2,k+2))
line, = ax.plot([], [], lw=2, label='Approximation to f(x)')
last_term, = ax.plot([], [], lw=2, label='Latest term')
ax.legend()
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return (line,)
# animation function. This is called sequentially
def animate(i):
f_approx = np.zeros(len(x))
term = np.zeros(len(x))
for n in range(1,n_max[i]+1):
if n%2==1:
cn=4*k/np.pi/n
else:
cn=0
term = cn*np.sin(n*x)
f_approx+=cn*np.sin(n*x)
line.set_data(x, f_approx)
last_term.set_data(x,term)
ax.set_title('n=%d'%n_max[i])
return (line,)
n_max = list(range(1,60))
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=len(n_max), interval=100, blit=True)
# Note: below is the part which makes it work on Colab
rc('animation', html='jshtml')
anim
Notice the little wiggles around the corners of the square wave? This effect is called Gibbs phenomenon, which occur when a Fourier series is trying to accomodate a discontuinity.
Special properties of the Fourier series#
This idea doesn’t work for any arbitrary functions. The sin/cos series has some special properties. The most important property is that the series is orthogonal. The integral of any two of the terms multiplied together is zero.
These are relatively simple to prove with a little bit of trigonometry.
Other orthogonal series#
Most sequences wouldn’t have this property. However, there are other orthogonal series covered in section 11.6 of your textbook. These orthogonal series come up in engineering when solving various PDE’s in different geometries (rectangular, cylindrical, spherical etc).
You might see these ideas in heat/mass transfer (if solving for heat transfer in a spherical particle), reaction engineering (reactions in a spherical particle), or quantum mechanics in physical chemistry.
Note that the form of the solution is coming from the ODE we solved in separation of variables, so this should match the basis functions you’re using.
Tricks to make solutions easier#
Notice how we just did everything in terms of \(-\pi\) to \(\pi\) but the original PDE was from 0 to L? There are a few tricks to switch from period \(2\pi\) to period 2L, and to simplify the fourier series. These are covered in section 11.2 in the textbook, and you’ll have some simple examples on the next homework.
For example, if your function is odd (\(f(x)=-f(-x)\)), then you will get an expansion in terms of \(\sin\). If your function is even \(f(x)=f(-x)\), then you will get an expansion in terms of \(\cos\). Notice that the bar example we have is actually an expansion for a square wave, that happens to work for \(x=0\) to \(x=\pi\) for the heated bar.
Second example#
Calculate \(b_n\) in the Fourier series for \(f(x)=|x|\) (the absolute value of x). You might find the following integrals helpful (from integration by parts):
First, we solve for \(a_0\)
Next, we solve for \(a_n\)
Finally, \(b_n\). This function is even, so right away we know that \(b_n\) will be 0. However, you can still do the math and verify that.
We can also plot this series solution to verify it.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
x = np.linspace(-2*np.pi , 2*np.pi, 200)
# First set up the figure, the axis, and the plot element we want to animate
fig, ax = plt.subplots()
plt.close()
f = np.abs(x)
ax.plot(x,f,'--',label='f(x)')
ax.set_xlabel('x')
ax.set_ylabel('f(x)')
ax.set_ylim((-k-2,k+2))
line, = ax.plot([], [], lw=2, label='Approximation to f(x)')
last_term, = ax.plot([], [], lw=2, label='Latest term')
ax.legend()
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return (line,)
# animation function. This is called sequentially
def animate(i):
f_approx = np.zeros(len(x))
#Add in the first term
a0 = np.pi/2
f_approx+=a0
#Add in the current cos term
term = np.zeros(len(x))
for n in range(1,n_max[i]+1):
an=2/np.pi*((np.cos(n*np.pi)-1)/n**2)
term = an*np.cos(n*x)
f_approx+=an*np.cos(n*x)
#Plot the most recent term and the full approximation
line.set_data(x, f_approx)
last_term.set_data(x,term)
ax.set_title('n=%d'%n_max[i])
return (line,)
n_max = list(range(1,60))
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=len(n_max), interval=100, blit=True)
# Note: below is the part which makes it work on Colab
rc('animation', html='jshtml')
anim
Numerical solutions to PDE’s#
Numerical solutions to PDE’s can be very complicated, especially in multiple dimensions or complicated geometries. For example, fluid flow in a reactor with multiple mixing regions:
The governing engineering/physics is usually straightforward. However, setting up the boundary conditions and solving is very complicated. The standard approach is to use a small mesh of points will little finite volumes/elements, discretize the PDE into a linear problem and solve that. This is sufficiently complicated that commercial packages like openfoam
, comsol
, or ansys
(remember ansys hall?) are used. ansys generally has tools for each type of PDE you want to solve. comsol
you can type in the PDE more generally and solve. These are taught in the graduate course 06-663 Analysis and Modeling of Transport Phenomena.
Note - repeated roots in coupled ODE’s#
One thing we brushed over when solving linear coupled ODE’s is what happens when you have a repeated root. For example:
The eigenvalues of this matrix are \(\lambda=-1,-1\), which is a repeated root. There is a single eigenvalue $\(\vec{x}^{(1)}=\begin{bmatrix}-1\\1\end{bmatrix}\)\( We don't have enough information to construct the solution. The approach in 2nd order ODE's was to tack a \)t$ onto the solution
but that does not work here!
A second solution is
where \(\vec{\eta}\) is a generalized eigenvector that solves the problem:
Plugging in \(\lambda\) and \(\vec{x}^{(1)}\), we get
This tells us that \(x_1+x_2=-1\), so we can pick any eigenvector that satisfies this. Let’s choose \(\vec{x}^{(2)}=[0,-1]\). Our final solution will be