熱傳導與傅立葉

2022-05-06
Project

前言

微積分(其實是教basic fourier analysis)期末專題。
我研究的主題是熱傳導方程式 ,並使用python模擬熱傳導的模型。


介紹

傅立葉冷卻定律描述熱在介質中傳播的規律,可以用兩種形式表達:

  1. 微分形式,關注局部的熱傳導率

  2. 積分形式,關注流出/入部分介質表面的熱

我使用的熱傳導方程就是傅立葉冷卻定律 的微分形式,來描述區域內溫度的演化。


正文

有限一維模型-數學推導

考慮一個理想的長棍,若$t=0$時,長棍上各點的溫度可用$f(x)$表達。
若可用一函數$u(x,t)$表達長棍一點的溫度於特定時間,求$u(x,t)$。

可知熱傳導方程式為: $\frac{\partial u}{\partial t}=\alpha \nabla^2 u$
因此處模型只有一維,故式中laplacian可改為: $\frac{\partial u}{\partial t}=\alpha \frac{\partial^2 u}{\partial x^2}$
在物理意義上為溫度隨時間改變的速度正比於相鄰溫差之差。

使用Neumann boundary condition: $\frac{\partial u}{\partial x}(0,t)=\frac{\partial u}{\partial x}(l,t)=0$
當然也可以使用Dirichlet boundary condition: $u(0,t)=u(l,t)=0$,但這裡只展示Neumann條件下的推導而已。

用分離變數法,假設$u(x,t)=X(x)T(t)$。
從$\frac{\partial u}{\partial t}=\alpha \nabla^2u$可知,$X(x)T’(t)=\alpha X’’(x)T(t)$。
令$\lambda=\alpha T(t)/T’(t)= X(x)/X’’(x)$。

先解出$X(x)$的部分,$\lambda X’’(x)-X(x)=0$。
利用特稱方程解此ODE,$\lambda m^2-1=0\iff m=\pm\sqrt\frac1\lambda$,所以$X(x)$的通解為$X(x)=c_1e^{\frac x{\sqrt\lambda}}+c_2e^{-\frac x{\sqrt\lambda}}$

檢查邊界條件。
$X’(x)=\frac{c_1}{\sqrt\lambda}e^{\frac x{\sqrt\lambda}}-\frac{c_2}{\sqrt\lambda}e^{-\frac x{\sqrt\lambda}}$.
$X’(0)=\frac{c_1}{\sqrt\lambda}-\frac{c_2}{\sqrt\lambda}=0\Rightarrow c_1=c_2$
$X’(l)=\frac{c_1}{\sqrt\lambda}e^{\frac l{\sqrt\lambda}}-\frac{c_2}{\sqrt\lambda}e^{-\frac l{\sqrt\lambda}}=0=\frac{c_1}{\sqrt\lambda}(e^{\frac{l}{\sqrt\lambda}}-e^{-\frac{l}{\sqrt\lambda}})\Rightarrow e^{\frac{l}{\sqrt\lambda}}=e^{-\frac{l}{\sqrt\lambda}}$

分別討論$\lambda$的正負:
若$\lambda>0$,無解。
若$\lambda=0$,$X(x)=0, u(x,t)=0$,此為平凡解。
若$\lambda<0$, $e^{i\frac{l}{\sqrt{|\lambda|}}}=e^{-i\frac{l}{\sqrt{|\lambda|}}}$。
利用歐拉公式,上式化簡為$2isin(\frac l{\sqrt{|\lambda|}})=0\iff|\lambda|=\frac{l^2}{n^2\pi^2}$。
由此可知,$|\lambda|$必滿足$|\lambda|=\frac{l^2}{n^2\pi^2}$。

回頭解$T(t)$,$\alpha T(t)/T’(t)=\lambda\Rightarrow\alpha T(t)=\lambda T’(t)\Rightarrow\alpha dt=\frac{\lambda}{T}dT\Rightarrow\int\frac \alpha\lambda dt=\int\frac1{T}dT=\frac\alpha\lambda t=ln(T)\Rightarrow ce^{t\frac \alpha\lambda}=T(t)$

最後,利用上述的解得到$u(x,t)=X(x)T(t)=K_ne^{-\alpha t\frac{n^2\pi^2}{l^2}}cos(x\frac{n\pi}{l})$

但注意此處有自由變數$n$,而根據此PDE的線性性質(若有$n$個解$f_i$,則$\sum_{i=1}^n f_i$構成解空間),所以通解要寫成

$u(x,t)=\sum^{\infty}_{n=0} X_n(x)T_n(t)=\sum^\infty_{n=0}K_ncos(x\frac{n\pi}{l})e^{-\alpha t\frac{n^2\pi^2}{l^2}}$

當$t=0$時,$u(x,0)=f(x)=\sum^{\infty}_{n=0} X_n(x)T_n(t)=\sum^\infty_{n=0}K_ncos(x\frac{n\pi}{l})$

若改成用Dirichlet boundary condition,解則變為$u(x,t)=\sum^{\infty}_{n=0} X_n(x)T_n(t)=\sum^\infty_{n=0}K_nsin(x\frac{n\pi}{l})e^{-\alpha t\frac{n^2\pi^2}{l^2}}$

正是因為這兩種解都牽涉到將函數轉成用三角函數表達,而促使傅立葉發展出傅立葉級數。


有限一維模型-程式模擬

我是用python的matplotlib的套件來畫圖,想看這套件的用法請參考我以前的文章
定積分用黎曼和計算。

2D version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

eps = 1e-8

def sec(x):
return 1 / np.cos(x);

l = np.pi
maxn = 1000
K = [0] * maxn

# original function
def f(x):
#return np.piecewise(x, [np.logical_and(np.less_equal(x, 3), np.greater_equal(x,2)), np.logical_not(np.logical_and(np.less_equal(x, 3), np.greater_equal(x,2)))], [1, 0])
return np.cos(x)
#return np.cos(np.sin(x * np.pi / l))
#return 1/3 * (x**3) - l/2 * (x**2)
#return (1/5) * (x**5) - ((7 + l) / 4) * (x**4) + ((10 + 7 * l) / 3) * (x**3) - 5*l *(x**2)
#return np.sin(np.pi * x**2 / (2 * l**2))

# calculate coefficient cos series
def calKn_cos():
dl = l / maxn
for n in range(maxn):
for i in range(maxn):
K[n] += f(dl * i) * np.cos(np.pi * n * dl * i / l)
K[n] *= (2 * dl / l)
K[0] /= 2

# calculate coefficient sin series
def calKn_sin():
dl = l / maxn
for n in range(maxn):
for i in range(maxn):
K[n] += f(dl * i) * np.sin(np.pi * n * dl * i / l)
K[n] *= (2 * dl / l)

# calculate coefficient
def calKn(mode = "cos"):
if mode == "cos":
calKn_cos()
elif mode == "sin":
calKn_sin()
else:
print("error argument")

# solution function
def u(x, t, alpha = 1, maxn = 1000):
s = 0
for n in range(maxn):
s += K[n] * np.cos(x * n * np.pi / l) * np.exp(-alpha * t * (n * np.pi / l) ** 2)
return s

fig = plt.figure()

X = np.linspace(0.5, l - 0.5, 1024)

calKn()

plt.plot(X, u(X, 0, alpha = 0.5), label = 't=0')
plt.plot(X, u(X, 1, alpha = 0.5), label = 't=1')
plt.plot(X, u(X, 2, alpha = 0.5), label = 't=2')
plt.plot(X, u(X, 4, alpha = 0.5), label = 't=4')

plt.legend(loc = 0)

plt.show()

3D version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

eps = 1e-8

def sec(x):
return 1 / np.cos(x);

l = np.pi
maxn = 1000
K = [0] * maxn

# original function
def f(x):
#return np.piecewise(x, [np.logical_and(np.less_equal(x, 3), np.greater_equal(x,2)), np.logical_not(np.logical_and(np.less_equal(x, 3), np.greater_equal(x,2)))], [1, 0])
return np.cos(x)
#return np.cos(np.sin(x * np.pi / l))
#return 1/3 * (x**3) - l/2 * (x**2)
#return (1/5) * (x**5) - ((7 + l) / 4) * (x**4) + ((10 + 7 * l) / 3) * (x**3) - 5*l *(x**2)
#return np.sin(np.pi * x**2 / (2 * l**2))

def calKn_cos():
dl = l / maxn
for n in range(maxn):
for i in range(maxn):
K[n] += f(dl * i) * np.cos(np.pi * n * dl * i / l)
K[n] *= (2 * dl / l)
K[0] /= 2

def calKn_sin():
dl = l / maxn
for n in range(maxn):
for i in range(maxn):
K[n] += f(dl * i) * np.sin(np.pi * n * dl * i / l)
K[n] *= (2 * dl / l)

def calKn(mode = "cos"):
if mode == "cos":
calKn_cos()
elif mode == "sin":
calKn_sin()
else:
print("error argument")

def u(x, t, alpha = 1, maxn = 1000):
s = 0
for n in range(maxn):
s += K[n] * np.cos(x * n * np.pi / l) * np.exp(-alpha * t * (n * np.pi / l) ** 2)
return s

fig = plt.figure()
ax = plt.axes(projection = '3d')

X = np.linspace(0.5, l - 0.5, 1024)
T = np.linspace(0, 10, 512)

calKn()

X, T = np.meshgrid(X, T)
U = u(X, T, alpha = 0.5)

ax.set_xlabel('x')
ax.set_ylabel('t')
ax.set_zlabel('temperature')
ax.plot_surface(X, T, U, cmap = 'rainbow')

plt.show()

圖片:

$f(x) = cos(x), l = 2\pi, \alpha = 0.5$

$f(x) = cos(x), l = \pi, \alpha = 0.5$

$f(x) = cos(sin(x * \pi/l)), l = 5, \alpha = 0.5$

$f(x) = \frac15x^5 - \frac{7+l}4x^4+\frac{10+7l}3x^3 - 5lx^2, l=5, \alpha = 0.5$

$f(x) = \begin{cases}1&,|x-2.5|<0.5\\\\0&,\text{else}\end{cases}, l=5, \alpha = 0.5$
原函數為方波。可以很清楚的看見吉布斯現象。


無限長一維模型-數學推導

具邊界條件的模型,距離邊界越近的地方,誤差值越大,若改成使用”無限長”的長棍,則在有限區間內幾乎無誤差值。

因現在模型為無限長,故沒有邊界條件,但熱方程與初始條件仍一樣。

同樣,假設$u(x,t)=X(x)T(t)$,同樣也會得到$\lambda=\frac{X(x)}{X’’(x)}=\frac{\alpha T(t)}{T’(t)}$,也同樣要解$\lambda X’’(x)-X(x)=0$。
但與有限長的長棍不同處即在此。

由前一例可知$\lambda < 0$,$X(x)=c_1e^{i\frac{x}{\sqrt{|\lambda|}}}+c_2e^{-i\frac{x}{\sqrt{|\lambda|}}}$
因無邊界條件,所以需要用其他方法化簡$X(x)$。
把$X(x)$寫成$\frac{c_1+c_2}2e^{i\frac{x}{\sqrt{|\lambda|}}}+\frac{c_1+c_2}2e^{-i\frac{x}{\sqrt{|\lambda|}}}+\frac{c_1-c_2}2e^{i\frac{x}{\sqrt{|\lambda|}}}-\frac{c_1-c_2}2e^{-i\frac{x}{\sqrt{|\lambda|}}}$
得到$X(x)=(c_1+c_2)cos(\frac{x}{\sqrt{|\lambda|}})+i(c_1-c_2)sin(\frac{x}{\sqrt{|\lambda|}})=Acos(\frac{x}{\sqrt{|\lambda|}})+Bsin(\frac{x}{\sqrt{|\lambda|}})$

$T(t)$則與前例一樣,$T(t)=ce^{t\frac \alpha\lambda}$

得到特解$u_{\lambda}(x,t)=(A_{\lambda}cos(\frac{x}{\sqrt{|\lambda|}})+B_{\lambda}sin(\frac{x}{\sqrt{|\lambda|}}))e^{-\frac{t\alpha}{|\lambda|}}$。
為了讓他好看點,令$p=\frac1{\sqrt{|\lambda|}}$。
則特解變為: $u_p(x,t)=(A_{p}cos(px)+B_{p}sin(px))e^{-p^2t\alpha}$
依據這個PDE的線性性質,即乘上一常數($\Delta p$)後仍為解答,$\sum^\infty_{n=0}\Delta p u_{n\Delta p}(x,t)$為通解。

$\lim_{\Delta p\to 0}\sum^\infty_{n=0}\Delta p u_{n\Delta p}(x,t)=\int^\infty_0u_p(x,t)dp=\int^\infty_0(A_{p}cos(px)+B_{p}sin(px))e^{-p^2t\alpha}dp=u(x,t)$

當$t=0$時,$u(x,0)=f(x)=\int^\infty_0(A_{p}cos(px)+B_{p}sin(px))dp$,其中,$A_p=\frac1\pi\int^\infty_{-\infty}f(x)cos(px)dx$ and $B_p=\frac1\pi\int^\infty_{-\infty}f(x)sin(px)dx$

可以自己驗證看看,這個解仍符合原先的PDE。

上式可進一步變換成複數型傅立葉轉換,過程與一般的傅立葉積分變成複數型傅立葉轉換一樣,故在此省略,直接寫出公式:
$u(x,t)=\frac1{2\pi}\int^{\infty}_{-\infty}F(p)e^{ipx}e^{-t\alpha p^2}dp$ ,其中, $F(p)=\int^{\infty}_{-\infty}f(v)e^{-ipv}dv$

一般會認為到此結束,但實際上因為$e^{-t\alpha p^2}$這項的存在,導致可以”交換積分順序”。
為何如此?
根據Fubini’s theorem,只要 $\int^{\infty}_{-\infty}(\int^{\infty}_{-\infty}|(e^{ipx}e^{-t\alpha p^2})f(v)e^{-ipv}|dv)dp$ 與 $\int^{\infty}_{-\infty}(\int^{\infty}_{-\infty}|(e^{ipx}e^{-t\alpha p^2})f(v)e^{-ipv}|dp)dv$ 兩者都收斂,則
$\int^{\infty}_{-\infty}(\int^{\infty}_{-\infty}(e^{ipx}e^{-t\alpha p^2})f(v)e^{-ipv}dv)dp$ 與 $\int^{\infty}_{-\infty}(\int^{\infty}_{-\infty}(e^{ipx}e^{-t\alpha p^2})f(v)e^{-ipv}dp)dv$同樣收斂,且結果相同。

檢查 $\int^{\infty}_{-\infty}(\int^{\infty}_{-\infty}|e^{ipx}e^{-t\alpha p^2}f(v)e^{-ipv}|dv)dp$ 是否收斂:
$\int^{\infty}_{-\infty}(\int^{\infty}_{-\infty}|e^{ipx}e^{-t\alpha p^2}f(v)e^{-ipv}|dv)dp=\\\\\int^{\infty}_{-\infty}e^{-t\alpha p^2}(\int^{\infty}_{-\infty}|e^{ip(x-v)}f(v)|dv)dp=\\\\\int^{\infty}_{-\infty}e^{-t\alpha p^2}(\int^{\infty}_{-\infty}|f(v)cos(p(x-v))+if(v)sin(p(x-v))|dv)dp=\\\\\int^{\infty}_{-\infty}e^{-t\alpha p^2}(\int^{\infty}_{-\infty}\sqrt{f^2(v)cos^2(p(x-v))+f^2(v)sin^2(p(x-v))}dv)dp=\\\\\int^{\infty}_{-\infty}e^{-t\alpha p^2}(\int^{\infty}_{-\infty}|f(v)|dv)dp$

檢查 $\int^{\infty}_{-\infty}(\int^{\infty}_{-\infty}|e^{ipx}e^{-t\alpha p^2}f(v)e^{-ipv}|dp)dv$ 是否收斂的步驟與上面相似,故省略。

兩者收斂的條件相同,都是需要$f(x)$在$\Bbb R$上絕對收斂,而這也正是傅立葉轉換存在的條件之一。

注意一般使用傅立葉轉換不能交換積分順序,原因是因為 $\frac{1}{2\pi}\int^{\infty}_{-\infty}(\int^{\infty}_{-\infty}|f(v)e^{-i\omega v} e^{i\omega x}| dv) d\omega$ 並不收斂(實際上兩種皆不收斂)。

利用交換積分順序
$u(x,t)=\frac1{2\pi}\int^{\infty}_{-\infty}f(v)(\int^{\infty}_{-\infty}e^{ip(x-v)}e^{-t\alpha p^2}dp)dv$。

注意中間的積分項
$\int^{\infty}_{-\infty}e^{ip(x-v)}e^{-t\alpha p^2}dp=\int^\infty_{-\infty}e^{-t\alpha p^2}cos(p(x-v))+ie^{-t\alpha p^2}sin(p(x-v))\,dp$
其中虛部為奇函數,故可消去,變為$\int^\infty_{-\infty}e^{-t\alpha p^2}cos(p(x-v))dp$

這個不定積分無法用初等函數寫出,但其定積分有很多做法: 曲線積分、積分內取微分、級數展開。
這裡用”積分內取微分”來解:
簡化上面的積分為 $\int^\infty_0e^{-s^2}cos(bs)ds$,
$I(b)=\int^\infty_0e^{-s^2}cos(bs)ds$
$I’(b)=\int^\infty_0\frac{\partial}{\partial b}(e^{-s^2}cos(bs))ds=\int^\infty_0-se^{-s^2}sin(bs)ds=-\frac12 bI(b)$
解此ODE:
$I’(b)=-\frac12 bI(b)\Rightarrow I(b)=\frac{\sqrt\pi}2e^{-\frac{b^2}4}$

令上述積分的$t\alpha p^2=s^2$ and $b=\frac{x-v}{\sqrt{t\alpha}}$,所以,$\int^\infty_{-\infty}e^{-t\alpha p^2}cos(p(x-v))dp=\sqrt\frac{\pi}{t\alpha}e^{-\frac{(x-v)^2}{4t\alpha}}$

答案變為
$u(x,t)=\frac1{2\pi}\int^{\infty}_{-\infty}f(v)(\sqrt\frac{\pi}{t\alpha}e^{-\frac{(x-v)^2}{4t\alpha}})dv=\frac1{2\sqrt{\pi t \alpha}}\int^{\infty}_{-\infty}f(v)e^{-\frac{(x-v)^2}{4t\alpha}}dv$

因為$t$在分母,會令$t=0$時程式實作不方便,可以令$z=\frac{v-x}{2\sqrt{t\alpha}}$,則最終答案:


無限長一維模型-程式模擬

這裡牽涉到上下界無限的瑕積分,我寫了兩種方法:
第一是直接截斷,將上下界當作一個很大的值,再直接當定積分做;
第二是利用變數變換,考慮$\int_{\Bbb R}f(x)dx$,令$x=tan k$,則剛剛的積分可寫為$\int^{\pi/2}_{-\pi/2}f(tan k)\cdot sec^2(k)dk$,如此,上下界無限的積分就可變為一般的定積分,
(若要更精確,還可以將$[\pi/2-\Delta k, \pi/2]$以及$[-\pi/2,-\pi/2+\Delta k]$這兩個被捨去的區間一律當作$f(tan(\pm\pi/2\mp\Delta k))\cdot sec^2(\pm\pi/2\mp\Delta k)$計算)
(不過在我實際測試中,肉眼看不太出來兩種方法的差別,我猜測是因為收斂速度很快導致的。)

2D version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

eps = 1e-8

def sec(x):
return 1 / np.cos(x);

# original function
def f(x):
#return np.piecewise(x, [np.less_equal(abs(x), 1), np.logical_not(np.less_equal(abs(x), 1))], [1, 0])
return np.exp(-x**2)
#return np.sinc(x)

# tan(k)-subtitute version
def sub_u(x, t, alpha = 1, maxn = 10000):
dk = np.pi / maxn
coe = dk / np.sqrt(np.pi)
s = 0
for n in range(1, maxn):
s += f(x + 2 * np.tan(-np.pi / 2 + dk * n) * np.sqrt(t * alpha)) * np.exp(-np.tan(-np.pi / 2 + dk * n) ** 2) * (sec(-np.pi / 2 + dk * n) ** 2)

s += f(x + 2 * np.tan(-np.pi / 2 + dk) * np.sqrt(t * alpha)) * np.exp(-np.tan(-np.pi / 2 + dk) ** 2) * (sec(-np.pi / 2 + dk) ** 2)
s += f(x + 2 * np.tan(np.pi / 2 - dk) * np.sqrt(t * alpha)) * np.exp(-np.tan(np.pi / 2 - dk) ** 2) * (sec(np.pi / 2 - dk) ** 2)
s *= coe
return s

# non-subtitute version
def nonsub_u(x, t, alpha = 1, L = -100, R = 100, maxn = 10000):
dk = (R - L) / maxn
coe = dk / np.sqrt(np.pi)
s = 0
for n in range(1, maxn - 1):
s += f(x + 2 * (L + dk * n) * np.sqrt(t * alpha)) * np.exp(-(L + dk * n)**2)
s *= coe
return s

fig = plt.figure()

X = np.linspace(-10, 10, 1024)

plt.plot(X, sub_u(X, 0, alpha = 0.5), label = 't=0')
plt.plot(X, sub_u(X, 1, alpha = 0.5), label = 't=1')
plt.plot(X, sub_u(X, 2, alpha = 0.5), label = 't=2')
plt.plot(X, sub_u(X, 4, alpha = 0.5), label = 't=4')

plt.legend(loc = 0)
plt.show()

3D version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

eps = 1e-8

def sec(x):
return 1 / np.cos(x);

# original function
def f(x):
#return np.piecewise(x, [np.less_equal(abs(x), 1), np.logical_not(np.less_equal(abs(x), 1))], [1, 0])
#return np.exp(-x**2)
return np.sinc(x)

# tan(k)-subtitute version
def sub_u(x, t, alpha = 1, maxn = 10000):
dk = np.pi / maxn
coe = dk / np.sqrt(np.pi)
s = 0
for n in range(1, maxn):
s += f(x + 2 * np.tan(-np.pi / 2 + dk * n) * np.sqrt(t * alpha)) * np.exp(-np.tan(-np.pi / 2 + dk * n) ** 2) * (sec(-np.pi / 2 + dk * n) ** 2)

s += f(x + 2 * np.tan(-np.pi / 2 + dk) * np.sqrt(t * alpha)) * np.exp(-np.tan(-np.pi / 2 + dk) ** 2) * (sec(-np.pi / 2 + dk) ** 2)
s += f(x + 2 * np.tan(np.pi / 2 - dk) * np.sqrt(t * alpha)) * np.exp(-np.tan(np.pi / 2 - dk) ** 2) * (sec(np.pi / 2 - dk) ** 2)
s *= coe
return s

# non-subtitute version
def nonsub_u(x, t, alpha = 1, L = -100, R = 100, maxn = 10000):
dk = (R - L) / maxn
coe = dk / np.sqrt(np.pi)
s = 0
for n in range(1, maxn - 1):
s += f(x + 2 * (L + dk * n) * np.sqrt(t * alpha)) * np.exp(-(L + dk * n)**2)
s *= coe
return s

fig = plt.figure()
ax = plt.axes(projection = "3d")

X = np.linspace(-5, 5, 1024)
T = np.linspace(0, 10, 512)

X, T = np.meshgrid(X, T)
U = sub_u(X, T, alpha = 0.5)

ax.set_xlabel('x')
ax.set_ylabel('t')
ax.set_zlabel('temperature')
ax.plot_surface(X, T, U, cmap = "rainbow")
plt.show()

圖片:

$f(x)=e^{-x^2}, \alpha = 0.5$

直接截斷計算積分:

變數替換:

$f(x)=\begin{cases}1&,|x|<1\\\\0&,\text{else}\end{cases}, \alpha = 0.5$

直接截斷計算積分:

變數替換:

$f(x)=sinc(x), \alpha = 0.5$

直接截斷計算積分:

變數替換:


無限長一維模型-離散化 1

既然都有CFT了,那試試看DFT能不能也產生同樣的效果。
將原函數取樣成一個個數據點,接著就可以利用DFT以及非正常(?)IDFT來處理:

直接寫出答案: $u(n,t)=\frac1{L}\sum^{L-1}_{k=0}(\sum^{L-1}_{m=0}f[m]e^{-i\frac{2\pi}{L}km})e^{i\frac{2\pi}{L}nk}e^{-t\alpha k^2}$
其中$\hat{f}[k] = \sum^{L-1}_{m=0}f[m]e^{-i\frac{2\pi}{L}km}$就是DFT。


無限長一維模型-離散化 1-程式模擬:

離散型就沒畫成3D圖了,只有2D疊層圖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

eps = 1e-8

def sec(x):
return 1 / np.cos(x);

# original function
def f(x):
#return np.piecewise(x, [np.less_equal(abs(x), 1), np.logical_not(np.less_equal(abs(x), 1))], [1, 0])
return np.exp(-x**2)
#return np.sinc(x)

L = 100
W_L = np.exp(-2j * np.pi / L)
l, r = -3, 3

# sample of original function
# n shall be in [0, maxn - 1]
def samplef(n):
dk = (r - l) / L
return f(l + dk * n)

# naive DFT
def hatf(k):
s = 0
for m in range(L):
s += samplef(m) * (W_L**(k*m))
return s

# naive IDFT
def u(n, t, alpha = 1):
s = 0
for k in range(L):
s += hatf(k) * (W_L**(-k*n)) * np.exp(-t * alpha * (k**2))
s /= L
return s.real


fig = plt.figure()

X = np.array([*range(L)])
t0 = u(X, 0, alpha = 0.5)
t1 = u(X, 1, alpha = 0.5)
t2 = u(X, 2, alpha = 0.5)
t3 = u(X, 10, alpha = 0.5)

# map the [1, L) to (l, r)
X = X * ((r - l) / L)
X = X + l

plt.bar(X, t0, width = 0.1, label = 't=0', alpha = 0.3)
plt.bar(X, t1, width = 0.1, label = 't=1', alpha = 0.3)
plt.bar(X, t2, width = 0.1, label = 't=2', alpha = 0.3)
plt.bar(X, t3, width = 0.1, label = 't=10', alpha = 0.3)

plt.legend(loc = 0)

plt.show()

圖片:

$f(x)=e^{-x^2},\alpha = 0.5$

$f(x)=\begin{cases}1&,|x|<1\\\\0&,\text{else}\end{cases}, \alpha = 0.5$

$f(x)=sinc(x), \alpha = 0.5$

雖然看起來蠻正確的,但對比連續的模型誤差蠻大的,看下圖:

$f(x)=e^{-x^2},\alpha = 0.5$

離散模擬:

連續模擬:

我猜測是因為隨隨便便就把$e^{-t\alpha k^2}$離散化導致的。


無限長一維模型-離散化 2

仔細想想剛剛那樣不嚴謹的轉換有點糟糕,認真分析一下:
最開始的解為:
$u(x,t)=\frac1{2\pi}\int^{\infty}_{-\infty}(\int^{\infty}_{-\infty}f(v)e^{-ipv}dv)e^{ipx}e^{-t\alpha p^2}dp$

左思右想,這不就是$u(x, t)=\mathscr F^{-1}[\mathscr F[f(x)]\cdot e^{-t\alpha p^2}]$。
改寫一下,
先計算出
所以答案又可以寫成

先寫個連續的摺積試試看效果:

可以看見都沒有剛剛隨便離散導致的誤差問題。

那既然,連續的摺積可以了,那離散的呢?

先切樣本出來,假設我們是在$[l,r]$上切出$L$個樣本。
,其中

將摺積離散化後: $u(n,t)=\Delta k \sum_{m=0}^{2L-1}f[n-m]G_t[m]$

裡面的$\Delta k$是歸一化係數,一定要乘,否則數據會失準。


無限長一維模型-離散化 2-程式模擬

實作上有個小缺陷,當$t=0$時,會產生divided by zero exception所以我用$t=0.01$去逼近。
那如果從數學的角度看,當$\lim_{t\to0}$時,$\mathscr F^{-1}[e^{-t\alpha p^2}]=\delta(x)$
$(f * \delta)(x)=f(x)$,所以數學上是沒問題的。

很巧合地(應該是必然的結果),$\lim_{t\to0}\frac1{\sqrt{\pi t\alpha}}e^{-\frac{x^2}{4t\alpha}}$,可以寫成另一種形式(令$a=2\sqrt{t\alpha}$): $\lim_{a\to0}\frac1{a\sqrt{\pi}}e^{-\frac{x^2}{a^2}}$,而這正是$\delta(x)$在分佈意義上的定義。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

eps = 1e-8

def sec(x):
return 1 / np.cos(x);

# original function
def f(x):
return np.piecewise(x, [np.less_equal(abs(x), 1), np.logical_not(np.less_equal(abs(x), 1))], [1, 0])
#return np.exp(-x**2)
#return np.sinc(x)

# heat transfer characteristic function
def G(x, t, alpha):
return 0.5 * np.sqrt(1 / (t * alpha * np.pi)) * np.exp(-(x**2) / (4 * t * alpha))

L = 100
l, r = -3, 3

sf = np.array([0.0] * (3*L))
def samplef():
dk = (r - l) / L
for i in range(L):
sf[i] = f(l + dk * i)

sG = np.array([0.0] * (3*L))
def sampleG(t, alpha):
dk = (r - l) / L
for i in range(L):
sG[i] = G(l + dk * i, t, alpha)

def u(n, t, alpha = 1):
dk = (r - l) / L
if t == 0: return sf[n];
sampleG(t, alpha)
s = 0
for m in range(2 * L):
s += sf[n - m] * sG[m]
s *= dk
return s

fig = plt.figure()

# sample the original function first
samplef()
X = np.array([*range(2 * L)])

t0 = u(X, 0.01, alpha = 0.5)
t1 = u(X, 1, alpha = 0.5)
t2 = u(X, 4, alpha = 0.5)
t3 = u(X, 10, alpha = 0.5)
t4 = u(X, 50, alpha = 0.5)

# map the [1, 2L-1] to [2l, 2r)
X = X * ((r - l) / L)
X = X + l*2

plt.bar(X, t0, width = 0.1, label = 't=0.01', alpha = 0.3)
plt.bar(X, t1, width = 0.1, label = 't=1', alpha = 0.3)
plt.bar(X, t2, width = 0.1, label = 't=4', alpha = 0.3)
plt.bar(X, t3, width = 0.1, label = 't=10', alpha = 0.3)
plt.bar(X, t4, width = 0.1, label = 't=50', alpha = 0.3)

plt.legend(loc = 0)

plt.show()

圖片:

$f(x)=e^{-x^2}, l = -3, r = 3, L = 100, \alpha = 0.5$

$f(x)=\begin{cases}1&,|x|<1\\\\0&,\text{else}\end{cases}, l = -3, r = 3, L = 100, \alpha = 0.5$

$f(x)=sinc(x), l = -3, r = 3, L = 100, \alpha = 0.5$

顯然結果比隨便離散化好多了。


無限長一維模型-離散化-FFT ver.

那既然有摺積了,何不來點FFT加速?

,其中

$u(n,t)=(f*G_t)[n]=\mathscr F^{-1}[\mathscr F[f]\cdot\mathscr F[G_t]][n]$

將FFT套用進DFT,就可以加速整體運算。


無限長一維模型-離散化-FFT ver.-程式模擬

這裡我參考正向的蝶形網路,先將數據點重新排列,再不斷的計算小部分的傅立葉轉換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

eps = 1e-8

def sec(x):
return 1 / np.cos(x);

# original function
def f(x):
return np.piecewise(x, [np.less_equal(abs(x), 1), np.logical_not(np.less_equal(abs(x), 1))], [1, 0])
#return np.exp(-x**2)
#return np.sinc(x)

# heat transfer characteristic function
def G(x, t, alpha):
return 0.5 * np.sqrt(1 / (t * alpha * np.pi)) * np.exp(-(x**2) / (4 * t * alpha))

L = 256
l, r = -3, 3

sf = np.zeros((L), dtype = complex)
def samplef():
dk = (r - l) / L
for i in range(L):
sf[i] = f(l + dk * i)

sG = np.zeros((L), dtype = complex)
def sampleG(t, alpha):
dk = (r - l) / L
for i in range(L):
sG[i] = G(l + dk * i, t, alpha)

def fft(X, mode = 'fft'):
tmp = np.zeros((2, (Len:=len(X))), dtype = complex)
tmp[0] = np.copy(X)

# reorder
N = len(tmp[0])
j = 0
for i in range(1, N):
k = N >> 1
while not ((j := j ^ k) & k):
k >>= 1
if (i > j):
tmp[0][i], tmp[0][j] = tmp[0][j], tmp[0][i]

# fft
N = 2
W = np.exp((-1 if mode == 'fft' else 1) * 2j * np.pi / N)
lev = 0
while N <= Len:
for i in range(Len):
if i % N < (N >> 1):
base = (i // N) * N
tmp[(lev & 1) ^ 1][(i + (1 << lev)) % N + base] += tmp[lev & 1][i]
tmp[(lev & 1) ^ 1][i % N + base] += tmp[lev & 1][i]
else:
base = (i // N) * N
tmp[(lev & 1) ^ 1][(i + (1 << lev)) % N + base] += tmp[lev & 1][i] * ( W ** ((i + (1 << lev)) % N) )
tmp[(lev & 1) ^ 1][i % N + base] += tmp[lev & 1][i] * ( W ** (i % N) )
tmp[lev & 1] = np.zeros((Len))
W **= 0.5
lev += 1
N <<= 1

return tmp[(lev) & 1]

def ifft(X):
return fft(X, mode = 'ifft') / len(X)

def calu(t, alpha):
dk = (r - l) / L
if t == 0: return sf[n];
sampleG(t, alpha)

cpsf = np.append(sf, np.zeros((L)))
cpsG = np.append(sG, np.zeros((L)))
s = ifft(fft(cpsf) * fft(cpsG))
s *= dk
return s.real

def u(n, t, alpha = 1):
s = calu(t, alpha)
return s[n]

fig = plt.figure()

samplef()
X = np.array([*range(2 * L)])

t0 = u(X, 0.01, alpha = 0.5)
t1 = u(X, 1, alpha = 0.5)
t2 = u(X, 4, alpha = 0.5)
t3 = u(X, 10, alpha = 0.5)
t4 = u(X, 50, alpha = 0.5)

X = X * ((r - l) / L)
X = X + l*2

plt.bar(X, t0, width = 0.1, label = 't=0.01', alpha = 0.3)
plt.bar(X, t1, width = 0.1, label = 't=1', alpha = 0.3)
plt.bar(X, t2, width = 0.1, label = 't=4', alpha = 0.3)
plt.bar(X, t3, width = 0.1, label = 't=10', alpha = 0.3)
plt.bar(X, t4, width = 0.1, label = 't=50', alpha = 0.3)

plt.legend(loc = 0)

plt.show()

圖片:

$f(x)=e^{-x^2}, l = -3, r = 3, L = 256, \alpha = 0.5$

$f(x)=\begin{cases}1&,|x|<1\\\\0&,\text{else}\end{cases}, l = -3, r = 3, L = 256, \alpha = 0.5$

$f(x)=sinc(x), l = -3, r = 3, L = 256, \alpha = 0.5$


source code

https://github.com/OEmiliatanO/heat_transfer_simulation


後記

這次的專題做的特別久,大概花了我3、4天的時間,但也收穫頗豐。
複習了以前做過的ODE解法,也學到了一點PDE解法、Leibniz integral rule、還有數值方法的技巧。
雖然推導過程不算太嚴謹,有一些步驟我沒有辦法判斷是否合法,例如從有限變成無限時,要乘上一常數,然後取極限,且這個常數是解的參數,從直覺上來講,這感覺就非法的,但結果是對的。
不確定其他類似的PDE也可以這樣做,希望有人可以指點迷津。

最後的離散化應該是我苦惱最久的,前面的連續版本只花一個晚上就推導出來了,之後就開始寫code、debug,寫完之後才處理離散化的問題,接著想了一整天才想到怎麼好好離散化。

blog的上一篇跟這篇隔了整整一年呢。

2023.5.17 更: 網站的render好像有改版,有一些數學公式都壞了,所以換成圖片。