Psuedospectral methods for solution of PDEs
\[ \frac{\partial u}{\partial t} = -\frac{\partial}{\partial x}\left(\Gamma\frac{\partial u}{\partial x}\right) + S \]
\[ u(t,x) = \sum_{n=0}^{N-1} c_n(t)\phi_n(x) \]
Periodic with period \(L\) (domain length)
\[ \phi_n(x) = e^{2\pi inx/L} \]
Nonperiodic with \(-1\le x\le 1\)
\[ \phi_n(x) = T_n(x) \]
\[ u_j = \sum_{n=0}^{N-1} c_n(t)\phi_n(x_j) \]
\[ \frac{\partial u_j}{\partial t} = -\frac{\partial}{\partial x}\left(\Gamma_j\frac{\partial u_j}{\partial x}\right)_j + S_j \]
\[ u_j = \sum_{n=0}^{N-1} c_n(t)\phi_n(x_j) \] \[ \phi_n(x)=e^{2\pi inx/L} \]
\[ \color{blue}{\rightarrow u_j = \sum_{n=0}^{N-1} c_n(t)e^{2\pi inx_j/L}} \]
\[\cos(2\pi nx/L)\]
Plotting all the cosine and sine waves gives
\[u(x) = a_0 + \sum_{n=1}^{N/2}[a_n\cos(2\pi nx/L)) + b_n\sin(2\pi nx/L)]\]
Complex form using Euler identities \[e^{i\theta} = \cos(\theta) + i\sin(\theta)\] \[\cos(\theta) = \frac{1}{2}(e^{i\theta} + e^{-i\theta})\] \[\sin(\theta) = \frac{1}{2i}(e^{i\theta} - e^{-i\theta})\] Insert into the above equation \(\Rightarrow\)
\[\Leftarrow u(x) = a_0 + \sum_{n=1}^{N/2}c_ne^{2\pi inx/L} + \sum_{n=1}^{N/2}k_ne^{-2\pi inx/L}\]
where \[c_n = \frac{a_n}{2} + \frac{b_n}{2i} = \frac{a_n}{2} - \frac{b_ni}{2}\] \[k_n = \frac{a_n}{2} - \frac{b_n}{2i} = \frac{a_n}{2} + \frac{b_ni}{2}\]
\[u(x) = \sum_{n=-N/2}^{N/2}c_ne^{2\pi inx/L}\]
\[ c_n = \begin{cases} a_0 & \text{for } n=0 \\ a_n/2 + b_ni/2 & \text{for } n<0, \\ a_n/2 - b_ni/2 & \text{for } n>0 \end{cases} \]
\[\color{blue}{u(x) = \sum_{n=-N/2+1}^{N/2}c_ne^{2\pi inx/L}}\]
IDFT: at \(x_j\), our equation is the inverse discrete Fourier Transform,
\[\color{blue}{u_j = \sum_{n=-N/2+1}^{N/2}c_ne^{2\pi inj/N}}\]
DFT: the discrete Fourier transform is
\[\color{blue}{c_n = \frac{1}{N}\sum_{j=0}^{N-1}u_je^{-2\pi inj/N}}\]
Evaluate using fast transforms (fft, ifft), denoted \(\mathbf{c} = \mathcal{F}(\mathbf{u})\), and \(\mathbf{u} = \mathcal{F}^{-1}(\mathbf{c})\).
DFT/IDFT forms given are not unique
Show that
\[\sum_{n=-N/2+1}^{N/2}c_ne^{2\pi inj/N} = \sum_{m=0}^{N-1}\hat{c}_m e^{2\pi imj/N}\]
\[\sum_{n=-N/2+1}^{N/2}c_ne^{2\pi inj/N} = \sum_{m=0}^{N-1}\hat{c}_m e^{2\pi imj/N}\]
Points shown for sums over \(n\) and \(m\) on the left and right for \(j=1\)
Evaluate derivatives at points \(x_j\), denoted \(\partial u_j/\partial x\).
\[u(x) = \sum_{n=-N/2+1}^{N/2}c_ne^{2\pi inx/L}\] \[\frac{\partial u}{\partial x} = \sum_{n=-N/2+1}^{N/2}(2\pi in/L)c_ne^{2\pi inx/L}\] \[\frac{\partial^2 u}{\partial x^2} = \sum_{n=-N/2+1}^{N/2}-(2\pi n/L)^2c_ne^{2\pi inx/L}\]
\[\frac{\partial \mathbf{u}}{\partial x} = \mathcal{F}^{-1}[(2\pi i\mathbf{n}/L)\odot \mathcal{F}(\mathbf{u})]\] \[\frac{\partial^2 \mathbf{u}}{\partial x^2} = \mathcal{F}^{-1}[(2\pi i\mathbf{n}/L)^2\odot \mathcal{F}(\mathbf{u})]\]
In a PDE \[\frac{\partial u}{\partial t} = -\frac{\partial{f}}{\partial x},\]
with diffusive flux \[f=-\Gamma(u)\frac{du}{dx}\] we have
\[\frac{\partial \mathbf{f}}{\partial x} = \mathcal{F}^{-1}[(2\pi i\mathbf{n}/L)\odot\mathcal{F}(\mathbf{f})]\] \[\mathbf{f} = -\Gamma(\mathbf{u})\odot\mathcal{F}^{-1}[(2\pi i\mathbf{n}/L)\odot\mathcal{F}(\mathbf{u})]\]
Or, on insertion, \[\frac{\partial\mathbf{u}}{\partial t} = -\mathcal{F}^{-1}[(2\pi i\mathbf{n}/L)\odot\mathcal{F}\{\mathbf{ \Gamma(\mathbf{u})\odot\mathcal{F}^{-1}[(2\pi i\mathbf{n}/L)\odot\mathcal{F}(\mathbf{u})] }\}]\]
\[\frac{\partial u}{\partial t} = -u\frac{\partial u}{\partial x} + \nu\frac{\partial^2u}{\partial x^2}.\]
\[u(x,t) = -2\nu\frac{\partial}{\partial x}\ln\left\{\frac{1}{\sqrt{4\pi\nu t}}\int_{-\infty}^\infty\exp\left[-\frac{(x-x^\prime)^2}{4\nu t}-\frac{1}{2\nu}\int_0^{x^\prime}u_0(x^{\prime\prime})dx^{\prime\prime}\right]dx^\prime\right\}\]
Spectral solution \[\frac{d\mathbf{u}}{dt} = -\mathbf{u}\odot\mathcal{F}^{-1}\left(\frac{2\pi i\mathbf{n}}{L}\odot\mathcal{F}(\mathbf{u})\right) + \nu \mathcal{F}^{-1}\left(\left(\frac{2\pi i\mathbf{n}}{L}\right)^2\odot\mathcal{F}(\mathbf{u})\right)\]
from scipy.integrate import odeint
from scipy.fft import fft, ifft
def spectral(nx):
L = 10.0
v = 0.1 # use same value as the exact solution above
tend = 10 # use same value as the exact solution above
#---------- solution domain, initial condition
dx = L/nx # not L/(nx-1)
x = np.linspace(0.0, L-dx, nx)
u0 = np.cos(2*np.pi*x/L) + 2
#---------- solve the problem
def rates(u, t):
N = len(u)
n = np.arange(N); n[int(N/2)+1:]-= N # n[int(N/2):]-=N
return -u*ifft(2*np.pi*1j*n/L*fft(u)).real - \
v*ifft((2*np.pi*n/L)**2*fft(u)).real
t = np.linspace(0,tend,11)
u = odeint(rates, u0, t)
return x, u, u0
#---------- (Now, call spectral(32) to solve, then plot result, not shown)
def FD(nx, upwind=False):
L = 10.0
v = 0.1 # use same value as the exact solution above
tend = 10 # use same value as the exact solution above
#---------- solution domain, initial condition
dx = L/nx # not L/(nx-1)
x = np.linspace(0.0, L-dx, nx)
u0 = np.cos(2*np.pi*x/L) + 2
#---------- solve the problem
i = np.arange(nx)
ip = i+1; ip[-1] = 0
im = i-1; im[0] = nx-1
def rates(u, t):
if upwind: # upwind on the advective term, 1st order
return -u*(u - u[im])/dx + v/dx/dx*(u[im] - 2*u + u[ip])
else: # central difference, 2nd order
return -u*(u[ip] - u[im])/2/dx + v/dx/dx*(u[im] - 2*u + u[ip])
t = np.linspace(0,tend,11)
u = odeint(rates, u0, t)
return x, u, u0
1D basis functions: \(\phi_n(x)=e^{2\pi inx/L}\)
2D basis functions: \(\phi_{n,m}(x,y) = e^{2\pi i(nx/L_x+my/L_y)} = e^{2\pi inx/L_x}e^{2\pi imy/L_y}\)
The IDFT and DFT are given by
\[\begin{align} &\text{IDFT:}\phantom{xxx}u_{j,k} = \sum_{n=-N_n/2+1}^{N_n/2}\left[ \sum_{m=-N_m/2+1}^{N_m/2} c_{n,m}e^{2\pi imy_k/L_y}\right] e^{2\pi inx_j/L_x} \\ &\text{DFT:}\phantom{Ixxx}c_{n,m} = \frac{1}{N_x}\sum_{j=0}^{N_x-1}\underbrace{\left[ \frac{1}{N_y} \sum_{k=0}^{N_y-1} u_{j,k}e^{-2\pi imy_k/L_y} \right]}_{\tilde{c}_{j,m}} e^{-2\pi inx_j/L_x} \end{align}\]
from scipy.fft import fft, ifft, fft2, ifft2
#---------- 2D DFT, same result as fft2(u)
def fft2D(u):
c = np.empty_like(u, dtype=np.complex128)
for j in range(Nx):
c[j,:] = fft(u[j,:])
for m in range(Nm):
c[:,m] = fft(c[:,m])
return c
#---------- 2D IDFT, same result as ifft2(c)
def ifft2D(c):
u = np.empty_like(c, dtype=np.complex128)
for n in range(Nn):
u[n,:] = ifft(c[n,:])
for k in range(Ny):
u[:,k] = ifft(u[:,k])
return u
\[ \frac{\partial u_{j,k}}{\partial x} = \sum_{n=-N_n/2+1}^{N_n/2}\frac{2\pi in}{L_x}\left[ \sum_{m=-N_m/2+1}^{N_m/2} c_{n,m}e^{2\pi imy_k/L_y}\right] e^{2\pi inx_j/L_x} \]
\[\frac{\partial \mathbf{u}_k}{\partial x} = \mathcal{F}^{-1}[(2\pi i\mathbf{n}/L_x)\odot\mathcal{F}(\mathbf{u}_k)]\]
\[ \frac{\partial u_{j,k}}{\partial x} = \sum_{n=-N_n/2+1}^{N_n/2}\frac{2\pi in}{L_x}\left[ \sum_{m=-N_m/2+1}^{N_m/2} c_{n,m}e^{2\pi imy_k/L_y}\right] e^{2\pi inx_j/L_x} \]
Insert the 2D DFT for \(c_{n,m}\). First change \(j\) to \(J\) and \(k\) to \(K\) to avoid clashing indices \[ \frac{\partial u_{j,k}}{\partial x} = \frac{1}{N_xN_y}\sum_n\frac{2\pi i n}{L_x}e^{2\pi inx_j/L_x}\sum_J\sum_K u_{J,K}e^{-2\pi inx_J/L_x}\sum_me^{2\pi i\frac{m}{N_y}(k-K)} \]
\[\frac{\partial u}{\partial t} = \Gamma\frac{\partial^2u}{\partial x^2} +\Gamma\frac{\partial^2u}{\partial x^2}\]
import numpy as np
import matplotlib.pyplot as plt
from numpy.fft import fft, ifft, fft2
from IPython.display import display, clear_output
import time
#--------- setup
Lx = 2.0*np.pi
Ly = 4.0*np.pi
Nx = 20
Ny = 40
G = 1.0
trun = 1.0
ntimes = 31
dx = Lx/Nx
dy = Ly/Ny
x = np.linspace(0.0, Lx-dx, Nx)
y = np.linspace(0.0, Ly-dy, Ny)
X,Y = np.meshgrid(x,y)
X = X.T; Y = Y.T
IJ = np.ix_ # for convenient 2D indexing
u0 = np.zeros((Nx,Ny))
u0[IJ(np.arange(5,15), np.arange(10,30))] = 1.0
#--------- define PDE right-hand-side
def rates(uu, t):
u = np.reshape(uu, (Nx,Ny)) # convert 1D array to 2D for eval
n = np.arange(Nx); n[int(Nx/2)+1:]-= Nx
m = np.arange(Ny); m[int(Ny/2)+1:]-= Ny
d2udx2 = np.empty((Nx, Ny))
d2udy2 = np.empty((Nx, Ny))
for k in range(Ny): d2udx2[:,k] = ifft( (2*np.pi*1j*n/Lx)**2 * fft(u[:,k])).real
for j in range(Nx): d2udy2[j,:] = ifft( (2*np.pi*1j*m/Ly)**2 * fft(u[j,:])).real
dudt = G*(d2udx2 + d2udy2)
return np.reshape(dudt, shape=Nx*Ny)
def rates_FD(uu, t):
u = np.reshape(uu, (Nx,Ny)) # convert 1D array to 2D for eval
i = np.arange(0,Nx); ip=i+1; ip[-1]=0; im=i-1; im[0]=Nx-1
j = np.arange(0,Ny); jp=j+1; jp[-1]=0; jm=j-1; jm[0]=Ny-1;
dudt = G*( (u[IJ(im,j)] - 2.0*u[IJ(i,j)] + u[IJ(ip,j)])/dx**2 +
(u[IJ(i,jm)] - 2.0*u[IJ(i,j)] + u[IJ(i,jp)])/dy**2 )
return np.reshape(dudt, shape=Nx*Ny)
#--------- solve the ODE system (method of lines)
uu0 = np.reshape(u0, shape=Nx*Ny) # convert 2D array to 1D for solve
times = np.linspace(0,trun, ntimes)
uu = odeint(rates, uu0, times) # solve spectral
#uu = odeint(rates_FD, uu0, times) # solve finite difference (2nd order central)
\[ u_j = \sum_{n=0}^{N-1} c_n(t)\phi_n(x_j) \] \[ \phi_n(x)=T_n(x) \]
\[ \color{blue}{\rightarrow u_j = \sum_{n=0}^{N-1} c_n(t)T_n(x_j)} \]
\(T_n(x)\) is the Chebyshev polynomial of the first kind
The first seven Chebyshev polynomials of the first kind are
\[\begin{align} &T_0(x) = 1 \\ &T_1(x) = x \\ &T_2(x) = 2x^2-1 \\ &T_3(x) = 4x^3-3x \\ &T_4(x) = 8x^4-8x^2+1 \\ &T_5(x) = 16x^5-20x^3+5x \\ &T_6(x) = 32x^6-48x^4+18x^2-1 \\ \end{align}\]
The Chebyshev polynomials are defined as \[ T_n(\cos\theta) = \cos(n\theta) \] Show how these are related to polynomials
\(T_n(x)\) is defined using a recurrence relation given \(T_0\) and \(T_1\): \[T_n(x) = 2xT_{n-1}(x) - T_{n-2}(x)\]
Euler’s identity: \[(e^{i\theta})^n = e^{in\theta} = (\cos\theta + i\sin\theta)^n = \cos{n\theta} + i\sin(n\theta)\] Rearrange to \[\cos(n\theta) = e^{in\theta} - i\sin(n\theta) = (\cos\theta + i\sin\theta)^n -i\sin(n\theta)\] Evaluate \(T_2(x)\) and use \(\cos^2\theta + \sin^2\theta = 1\)
\[\begin{align} T_2(x) &= T_2(\cos\theta) = \cos(2\theta) \\ &= (\cos\theta + i\sin\theta)^2 - i\sin(2\theta) \\ &= \cos^2\theta - \sin^2\theta + \color{red}{[2i\cos\theta\sin\theta - i\sin(2\theta)]} \\ &= \cos^2\theta - (1-\cos^2\theta) \\ &= 2\cos^2\theta - 1 \\ &= 2x^2 - 1 \end{align}\]
Let \(y=\sin\theta\). Then \[T_n = \cos(n\theta) = (\cos\theta + i\sin\theta)^n -\color{red}{i\sin(n\theta)}= (x+iy)^n.\]
Evaluating this for higher \(n\), shows a pattern, such as
\[\begin{align} T_6 = \binom{6}{0}x^6y^0i^0 + &\color{red}{\binom{6}{1}x^5y^1i^1} + \binom{6}{2}x^4y^2i^2 + \color{red}{\binom{6}{3}x^3y^3i^3} + \\ &\binom{6}{4}x^2y^4i^4 + \color{red}{\binom{6}{5}x^1y^5i^5} + \binom{6}{6}x^0y^6i^6 \end{align}\]
This pattern gives, (using \(y^2=1-x^2\)):
\[T_n = \sum_{k=0}^{n/2}\binom{n}{2k}x^{n-2k}(x^2-1)^k\]
(But use the recurrence relation. Actually, just use library functions)
The Chebyshev polynomials are special and clearly related to the cosine function by definition.
Roots \(N\) roots of \(T_N(x)\) are at \[x_j = \cos\theta_j,\] \[\theta_j = \frac{\pi}{N}(j+1/2)\] with \(j=0,\,\ldots,\,N-1\)
Extrema \(N+1\) extrema of \(T_N(x)\), (min/max values) at \[x_j = \cos(\theta_j),\; \theta_j = \frac{\pi j}{N},\; j=0,\,\ldots,\,N\]
\[ u(t,x) = \sum_{n=0}^{N-1} c_n(t)T_n(x). \]
\[u_j = \sum_{n=0}^{N-1} c_nT_{n,j}.\]
\[ \left( \begin{matrix} u_0 \\ u_1 \\ u_2 \\ u_3 \end{matrix} \right) = \left[\begin{matrix} T_0(x_0) & T_1(x_0) & T_2(x_0) & T_3(x_0) \\ T_0(x_1) & T_1(x_1) & T_2(x_1) & T_3(x_1) \\ T_0(x_2) & T_1(x_2) & T_2(x_2) & T_3(x_2) \\ T_0(x_3) & T_1(x_3) & T_2(x_3) & T_3(x_3) \end{matrix}\right] \left( \begin{matrix} c_0 \\ c_1 \\ c_2 \\ c_3 \end{matrix} \right) \]
Runge function
\[u(x) = \frac{1}{1+25x^2}\]
import numpy as np
import matplotlib.pyplot as plt
import numpy.polynomial.chebyshev as cheb
from scipy.special import eval_chebyt
from scipy.fft import fft, ifft
from scipy.fft import dct, idct, dctn, idctn
from scipy.integrate import odeint
import matplotlib.cm as cm
def urunge(x): # the Runge function for testing
return 1.0/(1.0+25*x*x)
def chebyterp(c,x):
return cheb.chebvander(x,len(c)-1) @ c # = sum_{n=0}^{N-1} c_nT_n(x)
xx = np.linspace(-1,1,1000) # lots of points for plotting
uu = urunge(xx)
\[ \newcommand{\joinR}{\hspace{-.1em}} \newcommand{\RI}{\text{I}} \newcommand{\RII}{\text{I}\hspace{-.1em}\text{I}} \]
def make_plot(N, aspect=(8,8), do_unif=True):
#--------- roots grid, option 1, direct calculation (same as option 2 below)
xcr = cheb.chebpts1(N)[::-1] # roots grid
ucr = urunge(xcr)
T = np.zeros((N,N))
for n in range(N):
for j in range(N):
T[n,j] = np.cos(np.pi*n/N*(j+0.5))
cr = np.linalg.inv(T.T) @ ucr
#--------- roots grid, option 2, us numpy.polynomial.chebyshev module
cr = cheb.chebinterpolate(urunge, N-1) # TT = T^T = cheb.chebvander(xcr, N-1); not needed...
#--------- extrema grid
xce = cheb.chebpts2(N)[::-1] # extrema grid
uce = urunge(xce)
for n in range(N):
for j in range(N):
T[n,j] = np.cos(np.pi*n*j/(N-1))
ce = np.linalg.inv(T.T) @ uce
#--------- uniform grid, polyfit
xu = np.linspace(-1,1,N)
p = np.polyfit(xu, urunge(xu), N-1)
#--------- plot results
plt.rcParams.update({'font.size': 16})
fig, ax = plt.subplots(1,1, figsize=aspect)
ax.plot(xx,urunge(xx), color='black', label='Exact', lw=2)
ax.plot(xx, chebyterp(cr,xx), '-', color='blue', label='Cheby roots')
ax.plot(xcr, ucr, 'o', ms=7, fillstyle='full', color='blue')
ax.plot(xx, chebyterp(ce,xx), '-', color='green', label='Cheby extrema')
ax.plot(xce, uce, '^', ms=7, fillstyle='full', color='green')
if do_unif:
ax.plot(xx, np.polyval(p,xx), '-', color='red', label='Uniform')
ax.plot(xu, urunge(xu), 's', ms=7, fillstyle='none', color='red')
ax.legend(frameon=False)
ax.set_xlabel('x')
ax.set_ylabel('u(x)');
#-------------
make_plot(9)
\(N=19\) without the uniform grid
Compute \(c_n\) by relating it to the discrete cosine tranform (DCT)
scipy.fft.dct
, scipy.fft.idct
Chebyshev interpolation on the roots grid:
\[u_j = \sum_{n=0}^{N-1}c_nT_n(x_{j,r}) = \sum_{n=0}^{N-1}c_n\cos\left(\frac{\pi n}{N}(j+1/2)\right)\]
\(\text{IDCT}_{\RII}\):
\[u_j = \frac{1}{N}\sum_{n=0}^{N-1}p_{\RII,n}a_n\cos\left(\frac{\pi n}{N}(j+1/2)\right)\]
Chebyshev interpolation is the \(\text{IDCT}_{\RII}\) with \(c_n = a_np_{\RII,n}/N\)
Summary \[\color{blue}{u_j = \sum_{n=0}^{N-1}c_nT_n(x_{j,r})}\] \[\color{blue}{c_n = \frac{a_np_{\RII,n}}{N}} \] \[\color{blue}{\mathbf{a} = \mathcal{C}_{\RII}(\mathbf{u})} \] \[\color{blue}{\mathbf{u} = \mathcal{C}_{\RII}^{-1}(\mathbf{a})}\]
The the extrema grid we use the Type-I DCT:
\[\begin{align} &\text{DCT}_\RI:\phantom{Ix}a_n = 2\sum_{j=0}^{N-1}u_j\cos\left(\frac{\pi nj}{N-1}\right) \\ &\text{IDCT}_\RI:\phantom{x}u_j = \frac{1}{N-1}\sum_{n=0}^{N-1}a_n p_{\RI,n}\cos\left(\frac{\pi nj}{N-1}\right) \end{align}\]
The Chebyshev interpolation on the extrema grid is
\[u_j = \sum_{n=0}^{N-1}c_nT_n(x_{j,e}) = \sum_{n=0}^{N-1}c_n\cos\left(\frac{\pi nj}{N-1}\right)\]
Comparing to the IDCT\(_{\RI}\) shows that \(c_n=a_np_{\RI,n}/(N-1)\)
Summary
\[\color{blue}{u_j = \sum_{n=0}^{N-1}c_nT_n(x_{j,e})}\] \[\color{blue}{c_n = \frac{a_np_{\RI,n}}{N-1}}\] \[\color{blue}{\mathbf{a} = \mathcal{C}_\RI(\mathbf{u})}\] \[\color{blue}{\mathbf{u} = \mathcal{C}_\RI(\mathbf{a})}\]
\[\frac{\partial u}{\partial x} = \sum_{n=0}^{N-1}c_n^\prime T_n(x),\]
\(c_n^\prime\) via recurrence relation (Boyd, 2\(^\text{nd}\) ed., pg 298) \[\begin{align} &(c^\prime_N=0) \\ &c^\prime_{N-1}=0 \\ &c^\prime_{n} = \gamma_n(c^\prime_{n+2} + 2(n+1)c_{n+1}),\;\; n=N-2,\ldots 0 \end{align}\] where \(\gamma_0=0.5\) and \(\gamma_{n\ne 0}=1\)
numpy.polynomial.chebyshev
function chebder
returns the size \(N-1\) array \(\mathbf{c}^\prime\) given \(\mathbf{c}\)
chebder
operates on \(c_n\) and is independent of the \(x\) grid usedN = 5
c = np.array([0.1, 0.2, -3, 4, 5])
cp = np.zeros(N)
for n in range(N-2,-1,-1):
if n < N-2: cp[n] = cp[n+2]
cp[n] += 2*(n+1)*c[n+1]
cp[0] *= 0.5
#-------- compare direct calculation to numpy.polynomial.chebyshev.chebder
print(cp)
print(np.append(cheb.chebder(c), 0.0))
[12.2 28. 24. 40. 0. ]
[12.2 28. 24. 40. 0. ]
Denote \(c^\prime = \mathcal{D}(c)\)
Roots grid:
\[\frac{\partial \mathbf{u}}{\partial x} = \mathcal{C}_{\RII}^{-1}\left[\frac{1}{\mathbf{p_{\RII}}}\odot\mathcal{D}\left(\mathbf{p_{\RII}}\odot\mathcal{C}_{\RII}(\mathbf{u})\right) \right]\]
Extrema grid:
\[\frac{\partial \mathbf{u}}{\partial x} = \mathcal{C}_\RI^{-1}\left[\frac{1}{\mathbf{p_\RI}}\odot\mathcal{D}\left(\mathbf{p_\RI}\odot\mathcal{C}_\RI(\mathbf{u})\right) \right]\]
n\(^\text{th}\) derivative, extrema grid:
\[\frac{\partial^n \mathbf{u}}{\partial x^n} = \mathcal{C}_\RI^{-1}\left[\frac{1}{\mathbf{p_\RI}}\odot\mathcal{D}^n\left(\mathbf{p_{\RI}}\odot\mathcal{C}_\RI(\mathbf{u})\right) \right]\]
chebder(c,n)
\[\frac{\partial u}{\partial t} = \Gamma\frac{\partial^2u}{\partial x^2} + S\]
Dirichlet boundaries, \(u(t,-1)=0\), \(u(t,1)=1\); zero initial condition \(u(0,x)=0\), \(S=1\)
Pseudospectral solution solved by the method of lines
\[\frac{\partial \mathbf{u}}{\partial t} = \Gamma\mathcal{C}_\RI^{-1}\left[\frac{1}{\mathbf{p_\RI}}\odot\mathcal{D}^2\left(\mathbf{p_\RI}\odot\mathcal{C}_\RI(\mathbf{u})\right) \right] + S\]
def chebyspec(N, trun):
G = 1.0 # Gamma
S = 1.0
uinit = np.zeros(N)
uinit[0] = 1.0 # Dirichlet BC at x=1
uinit[-1] = 0.0 # Dirichlet BC at x=-1
#---------------------
def rates(u,t):
p = np.ones(N); p[0]=p[-1]=0.5
#-------- d2u/dx2
a = dct(u, type=1)
c = a*p
cpp = np.append(cheb.chebder(c, 2),[0.0,0.0])
app = cpp / p
upp = idct(app, type=1)
#-------- compute the du/dt
dudt = G*upp + S
dudt[0] = dudt[-1] = 0 # don't change BC (make du/dt = 0)
return dudt
#---------------------
times = np.linspace(0,trun,9)
u = odeint(rates, uinit, times)
return times, u
#---------------------
N = 20
trun = 2.0
times, u = chebyspec(N, trun)
x = cheb.chebpts2(N)[::-1]
\[\frac{\partial \mathbf{u}}{\partial x} \equiv\mathbf{u}^\prime = \mathcal{C}_\RI^{-1}\left[\frac{1}{\mathbf{p_\RI}}\odot\mathcal{D}\left(\mathbf{p_\RI}\odot\mathcal{C}_\RI(\mathbf{u})\right) \right]\]
\[\frac{\partial \mathbf{u}}{\partial t} = \Gamma\mathcal{C}_\RI^{-1}\left[\frac{1}{\mathbf{p_\RI}}\odot\mathcal{D}\left(\mathbf{p_\RI}\odot\mathcal{C}_\RI(\mathbf{u}^\prime)\right) \right] + S\]
def chebyspec_Neumann(N):
G = 1.0 # Gamma
trun = 3 * 2.0/G
S = 1.0
x = cheb.chebpts2(N)[::-1]
uinit = np.zeros(N) # initial condition that satisfies u(-1)=0, u'(1)=1
for i in range(N):
uinit[i] = 2.0 + (x[i] - x[0])
#---------------------
def rates(u,t):
p = np.ones(N); p[0]=p[-1]=0.5
#-------- compute uprime
a = dct(u, type=1)
c = a*p
cp = np.append(cheb.chebder(c), 0.0)
ap = cp / p
up = idct(ap, type=1)
up[0] = 1.0 # impose Neumann BC at x=1
#-------- d2u/dx2
ap = dct(up, type=1)
cp = ap*p
cpp = np.append(cheb.chebder(cp), 0.0)
app = cpp/p
upp = idct(app, type=1)
#-------- compute the du/dt rates
dudt = G*upp + S
dudt[-1] = 0 # don't change the Dirichlet BC (make du/dt = 0)
return dudt
#---------------------
times = np.linspace(0,trun,9)
u = odeint(rates, uinit, times)
return times, u
#--------------------- solve the problem
N = 20
times, u = chebyspec_Neumann(N)
x = cheb.chebpts2(N)[::-1]
#--------------------- plot results (code not shown)
The Type-II cosine transforms (for the roots grid) in two dimensions are given by
\[\begin{align} &\text{IDCT}_{\RII}:\phantom{x}u_{j,k} = \frac{1}{N_x}\sum_{n=0}^{N_x-1}p_{\RII,n}\left[ \frac{1}{N_y}\sum_{m=0}^{N_y-1}p_{\RII,m}a_{n,m}\cos\left(\frac{\pi m}{N_x}(k+1/2)\right) \right]\cos\left(\frac{\pi n}{N_y}(j+1/2)\right) \\ &\text{DCT}_{\RII}:\phantom{Ix}a_{n,m} = 2\sum_{j=0}^{N_x-1}\underbrace{\left[ 2\sum_{k=0}^{N_y-1}u_{j,k}\cos\left(\frac{\pi m}{N_x}(k+1/2)\right) \right]}_{\tilde{a}_{j,m}}\cos\left(\frac{\pi n}{N_y}(j+1/2)\right) \\ \end{align}\]
The Type-I cosine transforms (for the extrema grid) in two dimensions are given by
\[\begin{align} &\text{IDCT}_{\RI}:\phantom{x}u_{j,k} = \frac{1}{N_x-1}\sum_{n=0}^{N_x-1}p_{\RI,n}\left[ \frac{1}{N_y-1}\sum_{m=0}^{N_y-1}p_{\RI,m}a_{n,m}\cos\left(\frac{\pi mk}{N_x-1}\right) \right]\cos\left(\frac{\pi nj}{N_y-1}\right) \\ &\text{DCT}_{\RI}:\phantom{Ix}a_{n,m} = 2\sum_{j=0}^{N_x-1}\underbrace{\left[ 2\sum_{k=0}^{N_y-1}u_{j,k}\cos\left(\frac{\pi mk}{N_x-1}\right) \right]}_{\tilde{a}_{j,m}}\cos\left(\frac{\pi nj}{N_y-1}\right) \\ \end{align}\]
type = 1
#---------- 2D DCT, type I or II
def dct2D(u):
a = np.empty_like(u)
Nx,Ny = np.shape(u)
for j in range(Nx):
a[j,:] = dct(u[j,:],type=type)
for m in range(Ny):
a[:,m] = dct(a[:,m],type=type)
return a
#---------- 2D IDCT, type II
def idct2D(a):
u = np.empty_like(a)
Nx,Ny = np.shape(a)
for n in range(Nx):
u[n,:] = idct(a[n,:],type=type)
for k in range(Ny):
u[:,k] = idct(u[:,k],type=type)
return u
#---------- (These give identical results to dctn and idctn)
a = np.random.rand(4,3)
print("Same?", np.allclose(dct2D(a), dctn(a, type=type)))
Same? True
\[\frac{\partial u}{\partial t} = \Gamma\frac{\partial^2u}{\partial x^2} +\Gamma\frac{\partial^2u}{\partial x^2}\]
Solve using pseudospectral and FD methods
doFD = False
#--------- setup
Lx = 2.0
Ly = 2.0
Nx = 21
Ny = 21
G = 1.0
trun = 0.2
ntimes = 11
if doFD:
x = np.linspace(-1.0, 1.0, Nx)
y = np.linspace(-1.0, 1.0, Ny)
else:
x = cheb.chebpts2(Nx)[::-1]
y = cheb.chebpts2(Ny)[::-1]
X,Y = np.meshgrid(x,y)
X = X.T; Y = Y.T
u0 = np.zeros((Nx,Ny))
for j in range(Nx):
for k in range(Ny):
if x[j]>=-0.5 and x[j]<=0.5 and y[k]>=-0.5 and y[k]<=0.5 : u0[j,k] = 1.0
#--------- define PDE for psuedospectral
def rates_cheby(uu, t):
u = np.reshape(uu, (Nx,Ny)) # convert 1D array to 2D for eval
d2udx2 = np.empty((Nx, Ny))
d2udy2 = np.empty((Nx, Ny))
for k in range(Ny):
c = dct(u[:,k], type=1); c[0]*=0.5; c[-1]*=0.5
app = np.append(cheb.chebder(c, 2),[0,0]); app[0]*=2; app[-1]*=2
d2udx2[:,k] = idct(app, type=1)
for j in range(Nx):
c = dct(u[j,:], type=1); c[0]*=0.5; c[-1]*=0.5
app = np.append(cheb.chebder(c, 2),[0,0]); app[0]*=2; app[-1]*=2
d2udy2[j,:] = idct(app, type=1)
dudt = G*(d2udx2 + d2udy2)
dudt[:,0] = dudt[:,-1] = 0.0
dudt[0,:] = dudt[-1,:] = 0.0
return np.reshape(dudt, shape=Nx*Ny)
#--------- define PDE for finite difference
def rates_FD(uu, t):
u = np.reshape(uu, (Nx,Ny)) # convert 1D array to 2D for eval
i = np.arange(1,Nx-1); ip=i+1; im=i-1
j = np.arange(1,Ny-1); jp=j+1; jm=j-1
dx = x[1]-x[0]
dy = y[1]-y[0]
dudt = np.zeros((Nx,Ny))
dudt[IJ(i,j)] = G*( (u[IJ(im,j)] - 2.0*u[IJ(i,j)] + u[IJ(ip,j)])/dx**2 +
(u[IJ(i,jm)] - 2.0*u[IJ(i,j)] + u[IJ(i,jp)])/dy**2 )
dudt[:,0] = dudt[:,-1] = 0.0
dudt[0,:] = dudt[-1,:] = 0.0
return np.reshape(dudt, shape=Nx*Ny)
#--------- solve the ODE system (method of lines)
uu0 = np.reshape(u0, shape=Nx*Ny) # convert 2D array to 1D for solve
times = np.linspace(0,trun, ntimes)
rates = rates_FD if doFD else rates_cheby
uu = odeint(rates, uu0, times) # solve the problem