Ordinary Differential Equation
Import package
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
plt.rc("font", family="Times New Roman")
from tqdm import tqdm
import imageio
from mpl_toolkits.mplot3d import Axes3D
Finite difference method
Differential approximation:
y
′
(
t
)
≈
y
(
t
+
h
)
−
y
(
t
−
h
)
2
h
y^{\prime}\left( t \right) \approx \frac{y\left( t+h \right) -y\left( t-h \right)}{2h}
y′(t)≈2hy(t+h)−y(t−h)
y
′
′
(
t
)
≈
y
(
t
+
h
)
−
2
y
(
t
)
+
y
(
t
−
h
)
h
2
y^{''}\left( t \right) \approx \frac{y\left( t+h \right) -2y\left( t \right) +y\left( t-h \right)}{h^2}
y′′(t)≈h2y(t+h)−2y(t)+y(t−h)
For boundary value problems (BVP):
{
y
′
′
=
4
y
y
(
0
)
=
1
y
(
1
)
=
3
\begin{cases} y^{''}=4y\\ y\left( 0 \right) =1\\ y\left( 1 \right) =3\\ \end{cases}
⎩
⎨
⎧y′′=4yy(0)=1y(1)=3
Linear BVP
The differential form of this ODE system is:
ω
i
+
1
−
2
ω
i
+
ω
i
−
1
h
2
−
4
ω
i
=
0
⟹
ω
i
−
1
+
(
−
4
h
2
−
2
)
ω
i
+
ω
i
+
1
=
0
\frac{\omega _{i+1}-2\omega _i+\omega _{i-1}}{h^2}-4\omega _i=0\Longrightarrow \omega _{i-1}+\left( -4h^2-2 \right) \omega _i+\omega _{i+1}=0
h2ωi+1−2ωi+ωi−1−4ωi=0⟹ωi−1+(−4h2−2)ωi+ωi+1=0
For the n-step Iterative method, n linear equations can be formed as follows:
(
−
4
h
2
−
2
1
0
⋯
⋯
⋯
0
1
−
4
h
2
−
2
1
0
⋯
⋯
0
0
1
−
4
h
2
−
2
1
0
⋯
0
0
0
⋱
⋱
⋱
0
0
⋮
⋮
0
1
−
4
h
2
−
2
1
0
⋮
⋮
⋮
0
1
−
4
h
2
−
2
1
0
0
0
0
0
1
−
4
h
2
−
2
)
(
ω
1
ω
2
ω
3
⋮
ω
n
−
2
ω
n
−
1
ω
n
)
=
(
−
1
0
0
⋮
0
0
−
3
)
\left( \begin{matrix}{} -4h^2-2& 1& 0& \cdots& \cdots& \cdots& 0\\ 1& -4h^2-2& 1& 0& \cdots& \cdots& 0\\ 0& 1& -4h^2-2& 1& 0& \cdots& 0\\ 0& 0& \ddots& \ddots& \ddots& 0& 0\\ \vdots& \vdots& 0& 1& -4h^2-2& 1& 0\\ \vdots& \vdots& \vdots& 0& 1& -4h^2-2& 1\\ 0& 0& 0& 0& 0& 1& -4h^2-2\\ \end{matrix} \right) \left( \begin{array}{l} \omega _1\\ \omega _2\\ \omega _3\\ \vdots\\ \omega _{n-2}\\ \omega _{n-1}\\ \omega _n\\ \end{array} \right) =\left( \begin{array}{l} -1\\ 0\\ 0\\ \vdots\\ 0\\ 0\\ -3\\ \end{array} \right)
−4h2−2100⋮⋮01−4h2−210⋮⋮001−4h2−2⋱0⋮0⋯01⋱100⋯⋯0⋱−4h2−210⋯⋯⋯01−4h2−21000001−4h2−2
ω1ω2ω3⋮ωn−2ωn−1ωn
=
−100⋮00−3
Please note the appearance of boundary values, and
h
=
(
b
−
a
)
/
(
n
+
1
)
h=(b-a)/(n+1)
h=(b−a)/(n+1), then solve this equation groups to get:
ω
0
=
1
,
ω
1
,
…
,
ω
n
,
ω
n
+
1
=
3
\omega_{0}=1, \omega_{1}, \dots, \omega_{n}, \omega_{n+1}=3
ω0=1,ω1,…,ωn,ωn+1=3
# Conjugate Gradient method to solve linear equation group
def ConjugateGradient(matrix, base):
n = len(matrix)
x = np.array([0] * n, dtype=float)
print("x0 = ", x.T.round(4))
x = np.transpose([x])
d = r = base - np.dot(matrix, x)
rT = np.transpose(r) # col -> row
dT = np.transpose(d)
for i in range(10000):
if np.linalg.norm(r, 2) < 1e-4:
print("Find solution: ", x.T.round(4))
break
rTr = np.dot(rT, r)[0, 0]
alpha = rTr/(np.dot(dT, np.dot(matrix, d))[0, 0])
x = x + np.dot(alpha, d)
print("x{} = ".format(i + 1), x.T.round(4))
r = r - np.dot(alpha, np.dot(matrix, d))
rT = np.transpose(r)
beta = np.dot(rT, r)[0, 0]/rTr
d = r + beta * d
dT = np.transpose(d)
return x
def func(t): # analytical solution
return (3 - np.power(np.e, -2))/(np.power(np.e, 2) - np.power(np.e, -2)) * np.exp(2 * t) + (np.power(np.e, 2) - 3)/(np.power(np.e, 2) - np.power(np.e, -2)) * np.exp(-2 * t)
n_step = 10
step_h = (1.0 - 0.0)/(n_step + 1)
b_value1, b_value2 = 1.0, 3.0 # boundary value: y(0) = 1.0, y(1) = 3.0
coefficient_matrix = np.array([-4 * np.power(step_h, 2) - 2, 1] + [0] * (n_step - 2))
for i in range(1, n_step - 1):
coefficient_matrix = np.vstack((coefficient_matrix, np.array([0] * (i - 1) + [1, -4 * np.power(step_h, 2) - 2, 1] + [0] * (n_step - 3 - (i - 1)))))
coefficient_matrix = np.vstack((coefficient_matrix, np.array([0] * (n_step - 2) + [1, -4 * np.power(step_h, 2) - 2])))
base = np.transpose([np.array([-1] + [0] * (n_step - 2) + [-3])])
omega = np.hstack((np.array([b_value1]), np.transpose(ConjugateGradient(coefficient_matrix, base)).squeeze(), np.array([b_value2])))
# plot:
plt.rcParams['xtick.direction'] = "in" # ticks direction
plt.rcParams['ytick.direction'] = "in"
axes=plt.gca()
axes.yaxis.set_major_locator(MaxNLocator(5)) # ticks num
axes.xaxis.set_major_locator(MaxNLocator(5))
plt.xticks(fontsize=20, fontweight="bold") # ticks font
plt.yticks(fontsize=20, fontweight="bold")
plt.xlabel("T", fontsize=22, fontweight="bold")
plt.ylabel("Y", fontsize=22, fontweight="bold")
plt.title("Finite difference method, n = 10", fontsize=24, fontweight="bold")
plt.scatter(np.linspace(0, 1, 12), func(np.linspace(0, 1, 12)), color="red", marker='o', s=100, label="Y")
plt.plot(np.linspace(0, 1, 100), func(np.linspace(0, 1, 100)), color="red", linewidth=3)
plt.scatter(np.linspace(0, 1, 12), omega, color="blue", marker='*', s=150, label="Omega")
# set legend:
legend_font = {
"family": "Times New Roman",
"style" : "normal",
"size" : 20,
'weight': "bold"
}
plt.legend(
bbox_to_anchor=(0.95, 0), loc="lower right",
frameon=False, prop=legend_font
)
plt.show()
Non-linear BVP
{
y
′
′
=
y
′
+
cos
y
y
(
0
)
=
0
y
(
π
)
=
1
\begin{cases} y^{''}=y^{\prime}+\cos y\\ y\left( 0 \right) =0\\ y\left( \pi \right) =1\\ \end{cases}
⎩
⎨
⎧y′′=y′+cosyy(0)=0y(π)=1
According to the finite difference method:
ω
i
+
1
−
2
ω
i
+
ω
i
−
1
h
2
−
ω
i
+
1
−
ω
i
−
1
2
h
−
cos
ω
i
=
0
\frac{\omega _{i+1}-2\omega _i+\omega _{i-1}}{h^2}-\frac{\omega _{i+1}-\omega _{i-1}}{2h}-\cos \omega _i =0
h2ωi+1−2ωi+ωi−1−2hωi+1−ωi−1−cosωi=0
⇒
(
1
+
1
2
h
)
ω
i
−
1
−
2
ω
i
(
1
−
1
2
h
)
ω
i
+
1
−
h
2
cos
ω
i
=
0
\Rightarrow \left( 1+\frac{1}{2}h \right) \omega _{i-1}-2\omega _i\left( 1-\frac{1}{2}h \right) \omega _{i+1}-h^2\cos \omega _i=0
⇒(1+21h)ωi−1−2ωi(1−21h)ωi+1−h2cosωi=0
Thus, n-dimensional nonlinear functions can be obtained:
F
(
ω
)
=
(
(
1
+
1
2
h
)
y
(
a
)
−
2
ω
i
(
1
−
1
2
h
)
ω
2
−
h
2
cos
ω
1
⋮
(
1
+
1
2
h
)
ω
i
−
1
−
2
ω
i
(
1
−
1
2
h
)
ω
i
+
1
−
h
2
cos
ω
i
⋮
(
1
+
1
2
h
)
ω
n
−
1
−
2
ω
i
(
1
−
1
2
h
)
y
(
b
)
−
h
2
cos
ω
n
)
F\left( \omega \right) =\left( \begin{array}{c} \left( 1+\frac{1}{2}h \right) y\left( a \right) -2\omega _i\left( 1-\frac{1}{2}h \right) \omega _2-h^2\cos \omega _1\\ \vdots\\ \left( 1+\frac{1}{2}h \right) \omega _{i-1}-2\omega _i\left( 1-\frac{1}{2}h \right) \omega _{i+1}-h^2\cos \omega _i\\ \vdots\\ \left( 1+\frac{1}{2}h \right) \omega _{n-1}-2\omega _i\left( 1-\frac{1}{2}h \right) y\left( b \right) -h^2\cos \omega _n\\ \end{array} \right)
F(ω)=
(1+21h)y(a)−2ωi(1−21h)ω2−h2cosω1⋮(1+21h)ωi−1−2ωi(1−21h)ωi+1−h2cosωi⋮(1+21h)ωn−1−2ωi(1−21h)y(b)−h2cosωn
corresponding Jacobi matrix:
D
F
(
ω
)
=
(
−
2
+
h
2
sin
ω
1
1
−
h
2
0
⋯
⋯
⋯
0
1
+
h
2
−
2
+
h
2
sin
ω
2
1
−
h
2
0
⋯
⋯
0
0
1
+
h
2
−
2
+
h
2
sin
ω
3
1
−
h
2
0
⋯
0
0
0
⋱
⋱
⋱
0
0
⋮
⋮
0
1
+
h
2
−
2
+
h
2
sin
ω
n
−
2
1
−
h
2
0
⋮
⋮
⋮
0
1
+
h
2
−
2
+
h
2
sin
ω
n
−
1
1
−
h
2
0
0
0
0
0
1
+
h
2
−
2
+
h
2
sin
ω
n
)
DF\left( \omega \right) =\left( \begin{matrix} -2+h^2\sin \omega _1& 1-\frac{h}{2}& 0& \cdots& \cdots& \cdots& 0\\ 1+\frac{h}{2}& -2+h^2\sin \omega _2& 1-\frac{h}{2}& 0& \cdots& \cdots& 0\\ 0& 1+\frac{h}{2}& -2+h^2\sin \omega _3& 1-\frac{h}{2}& 0& \cdots& 0\\ 0& 0& \ddots& \ddots& \ddots& 0& 0\\ \vdots& \vdots& 0& 1+\frac{h}{2}& -2+h^2\sin \omega _{n-2}& 1-\frac{h}{2}& 0\\ \vdots& \vdots& \vdots& 0& 1+\frac{h}{2}& -2+h^2\sin \omega _{n-1}& 1-\frac{h}{2}\\ 0& 0& 0& 0& 0& 1+\frac{h}{2}& -2+h^2\sin \omega _n\\ \end{matrix} \right)
DF(ω)=
−2+h2sinω11+2h00⋮⋮01−2h−2+h2sinω21+2h0⋮⋮001−2h−2+h2sinω3⋱0⋮0⋯01−2h⋱1+2h00⋯⋯0⋱−2+h2sinωn−21+2h0⋯⋯⋯01−2h−2+h2sinωn−11+2h000001−2h−2+h2sinωn
eps = 1e-4
n_step = 40
step_h = (np.pi - 0.0)/(n_step + 1) # h = (b - a)/(n + 1)
b_value1, b_value2 = 0.0, 1.0 # boundary value: y(0) = 1.0, y(1) = 3.0
def F(x, n_step, step_h):
a = np.array([(1 + step_h/2) * b_value1 - 2 * x[0, 0] + (1 - step_h/2) * x[1, 0] - np.power(step_h, 2) * np.cos(x[0, 0])])
b = np.array([
[(1 + step_h/2) * x[i - 1, 0] - 2 * x[i, 0] + (1 - step_h/2) * x[i + 1, 0] - np.power(step_h, 2) * np.cos(x[i, 0])] for i in range(1, n_step -1)
])
c = np.array([(1 + step_h/2) * x[n_step - 2, 0] - 2 * x[n_step - 1, 0] + (1 - step_h/2) * b_value2 - np.power(step_h, 2) * np.cos(x[n_step - 1, 0])])
return np.vstack((a, b, c))
def DF(x, n_step, step_h): # x.size() = (n, 1)
a = np.array([-2 + np.power(step_h, 2) * np.sin(x[0, 0]), 1 - step_h/2] + [0] * (n_step - 2), dtype=float)
b = np.array([
([0] * (i - 1) + [1 + step_h/2, -2 + np.power(step_h, 2) * np.sin(x[i, 0]), 1 - step_h/2] + [0] * (n_step - 3 - (i - 1))) for i in range(1, n_step - 1)
], dtype=float)
c = np.array([0] * (n_step - 2) + [1 + step_h/2, -2 + np.power(step_h, 2) * np.sin(x[n_step - 1, 0])], dtype=float)
return np.vstack((a, b, c))
def MultivariableNewton(n_step, step_h): # define F(x) and DF(x) of your system first
x = np.zeros((n_step, 1))
last_x = np.ones((n_step, 1))
print("x0 = ", x.T.round(4))
i = 0
while np.linalg.norm(x - last_x, np.inf) > eps:
i += 1
last_x = x
x = x - np.dot(np.linalg.inv(DF(x, n_step=n_step,step_h=step_h)), F(x, n_step=n_step,step_h=step_h))
print("x{} = ".format(i), x.T.round(4))
return x
omega = np.hstack((np.array([b_value1]), np.transpose(MultivariableNewton(n_step=n_step, step_h=step_h)).squeeze(), np.array([b_value2])))
# plot:
plt.rcParams['xtick.direction'] = "in" # ticks direction
plt.rcParams['ytick.direction'] = "in"
axes=plt.gca()
axes.yaxis.set_major_locator(MaxNLocator(5)) # ticks num
axes.xaxis.set_major_locator(MaxNLocator(5))
plt.xticks(fontsize=20, fontweight="bold") # ticks font
plt.yticks(fontsize=20, fontweight="bold")
plt.xlabel("T", fontsize=22, fontweight="bold")
plt.ylabel("Y", fontsize=22, fontweight="bold")
plt.title("Finite difference method, n = 40", fontsize=24, fontweight="bold")
plt.plot(np.linspace(0, np.pi, n_step + 2), omega, color="red", linewidth=3)
# set legend:
legend_font = {
"family": "Times New Roman",
"style" : "normal",
"size" : 20,
'weight': "bold"
}
plt.legend(
bbox_to_anchor=(0.95, 0), loc="lower right",
frameon=False, prop=legend_font
)
plt.show()
Collocation and Finite element method
The solution is given a functional form whose parameters are fit by the method.
Choose a set of basis functions
ϕ
1
(
t
)
,
…
,
ϕ
n
(
t
)
\phi_{1}(t), \dots, \phi_{n}(t)
ϕ1(t),…,ϕn(t), which may be polynomials, trigonometric functions, splines, or other simple functions. Then consider the possible solution:
y
(
t
)
=
∑
i
=
1
n
c
i
ϕ
i
(
t
)
y\left( t \right) =\sum_{i=1}^n{c_i\phi _i\left( t \right)}
y(t)=i=1∑nciϕi(t)
Collocation method
For BVP:
{
y
′
′
=
f
(
t
,
y
,
y
′
)
y
(
a
)
=
y
a
y
(
b
)
=
y
b
\begin{cases} y^{''}=f\left( t, y, y^{\prime} \right)\\ y\left( a \right) =y_a\\ y\left( b \right) =y_b\\ \end{cases}
⎩
⎨
⎧y′′=f(t,y,y′)y(a)=yay(b)=yb
Choose n points, beginning and ending with the boundary points
a
a
a and
b
b
b, say:
a
=
t
1
<
⋯
<
t
n
=
b
a=t_1<\cdots <t_n=b
a=t1<⋯<tn=b
Here, we choose the basis functions
ϕ
j
(
t
)
=
t
j
−
1
\phi_{j}(t)=t^{j-1}
ϕj(t)=tj−1 for
1
⩽
j
⩽
n
1\leqslant j\leqslant n
1⩽j⩽n. The solution will be of form:
y
(
t
)
=
∑
j
=
1
n
c
j
ϕ
j
(
t
)
=
∑
j
=
1
n
c
i
t
j
−
1
y\left( t \right) =\sum_{j=1}^n{c_j\phi _j\left( t \right)}=\sum_{j=1}^n{c_it^{j-1}}
y(t)=j=1∑ncjϕj(t)=j=1∑ncitj−1
We will write n equations in the
n
n
n unknowns
c
1
,
.
.
.
,
c
n
c_{1},...,c_{n}
c1,...,cn. The first and last are the boundary conditions:
i
=
1
:
∑
j
=
1
n
c
j
a
j
−
1
=
y
(
a
)
i
=
n
:
∑
j
=
1
n
c
j
b
j
−
1
=
y
(
b
)
i=1:\sum_{j=1}^n{c_ja^{j-1}}=y\left( a \right) \\ i=n:\sum_{j=1}^n{c_jb^{j-1}}=y\left( b \right)
i=1:j=1∑ncjaj−1=y(a)i=n:j=1∑ncjbj−1=y(b)
The remaining
n
−
2
n−2
n−2 equations come from the differential equation evaluated at
t
i
t_{i}
ti for
2
⩽
i
⩽
n
−
1
2\leqslant i\leqslant n−1
2⩽i⩽n−1:
∑
j
=
1
n
(
j
−
1
)
(
j
−
2
)
c
j
t
j
−
3
=
f
(
t
,
∑
j
=
1
n
c
j
t
j
−
1
,
∑
j
=
1
n
(
j
−
1
)
c
j
t
j
−
2
)
\sum_{j=1}^n{\left( j-1 \right) \left( j-2 \right) c_jt^{j-3}}=f\left( t,\sum_{j=1}^n{c_jt^{j-1}},\sum_{j=1}^n{\left( j-1 \right) c_jt^{j-2}} \right)
j=1∑n(j−1)(j−2)cjtj−3=f(t,j=1∑ncjtj−1,j=1∑n(j−1)cjtj−2)
Application
For BVP:
{
y
′
′
=
4
y
y
(
0
)
=
1
y
(
1
)
=
3
\begin{cases} y^{''}=4y\\ y\left( 0 \right) =1\\ y\left( 1 \right) =3\\ \end{cases}
⎩
⎨
⎧y′′=4yy(0)=1y(1)=3
Through collocation method:
i
=
1
:
∑
j
=
1
n
c
j
a
j
−
1
=
c
1
=
1
i=1:\sum_{j=1}^n{c_ja^{j-1}}=c_1=1
i=1:j=1∑ncjaj−1=c1=1
i
=
n
:
∑
j
=
1
n
c
j
b
j
−
1
=
∑
j
=
1
n
c
j
=
3
i=n:\sum_{j=1}^n{c_jb^{j-1}}=\sum_{j=1}^n{c_j}=3
i=n:j=1∑ncjbj−1=j=1∑ncj=3
∑
j
=
1
n
(
j
−
1
)
(
j
−
2
)
c
j
t
i
j
−
3
=
f
(
t
,
∑
j
=
1
n
c
j
t
i
j
−
1
,
∑
j
=
1
n
(
j
−
1
)
c
j
t
i
j
−
2
)
⇒
∑
j
=
1
n
[
(
j
−
1
)
(
j
−
2
)
c
j
t
i
j
−
3
−
4
t
i
j
−
1
]
c
j
=
0
\sum_{j=1}^n{\left( j-1 \right) \left( j-2 \right) c_jt_{i}^{j-3}}=f\left( t,\sum_{j=1}^n{c_jt_{i}^{j-1}},\sum_{j=1}^n{\left( j-1 \right) c_jt_{i}^{j-2}} \right) \\ \Rightarrow \sum_{j=1}^n{\left[ \left( j-1 \right) \left( j-2 \right) c_jt_{i}^{j-3}-4t_{i}^{j-1} \right] c_j}=0
j=1∑n(j−1)(j−2)cjtij−3=f(t,j=1∑ncjtij−1,j=1∑n(j−1)cjtij−2)⇒j=1∑n[(j−1)(j−2)cjtij−3−4tij−1]cj=0
which gives us a n-dimension linear equation:
(
1
0
⋯
⋯
0
⋯
⋯
(
j
−
1
)
(
j
−
2
)
t
i
j
−
3
−
4
t
i
j
−
1
⋯
⋯
1
1
⋯
⋯
1
)
(
c
1
⋮
c
n
)
=
(
1
⋮
3
)
;
1
⩽
j
⩽
n
,
2
⩽
i
⩽
n
−
1
\left( \begin{matrix} 1& 0& \cdots& \cdots& 0\\ \cdots& \cdots& \left( j-1 \right) \left( j-2 \right) t_{i}^{j-3}-4t_{i}^{j-1}& \cdots& \cdots\\ 1& 1& \cdots& \cdots& 1\\ \end{matrix} \right) \left( \begin{array}{c} c_1\\ \vdots\\ c_n\\ \end{array} \right) =\left( \begin{array}{c} 1\\ \vdots\\ 3\\ \end{array} \right) ; 1\leqslant j\leqslant n, 2\leqslant i\leqslant n-1
1⋯10⋯1⋯(j−1)(j−2)tij−3−4tij−1⋯⋯⋯⋯0⋯1
c1⋮cn
=
1⋮3
;1⩽j⩽n,2⩽i⩽n−1
where:
t
i
=
a
+
i
−
1
n
−
1
(
b
−
a
)
=
i
−
1
n
−
1
t_i=a+\frac{i-1}{n-1}\left( b-a \right) =\frac{i-1}{n-1}
ti=a+n−1i−1(b−a)=n−1i−1
def Householder(matrix):
A = np.transpose(matrix)
n = A.shape[1]
omega = np.array([np.linalg.norm(A[0], 2)] + [0] * (n - 1))
v = omega - A[0]
P = np.dot(np.transpose([v]), [v])/np.dot([v], np.transpose([v]))
H_hat = np.eye(n) - 2 * P
return H_hat
def AugmentH(h_hat, n):
r = n - len(h_hat)
return np.block([
[np.eye(r, r), np.zeros((r, len(h_hat)))],
[np.zeros((len(h_hat), r)), h_hat]
])
def HouseholderQRDecomposition(matrix):
A = np.transpose(matrix)
hid_r = np.eye(len(matrix))
state = matrix
for i in range(matrix.shape[1]):
h_hat = Householder(state)
H = AugmentH(h_hat, len(matrix))
hid_r = np.dot(H, hid_r)
state = np.dot(hid_r, matrix)
state = state[i + 1:, i + 1:]
if state.shape[0] < 2:
break
cnt = 0
for i in range(1, state.shape[0]):
if state[i][0] == 0:
cnt += 1
if cnt == state.shape[0] - 1:
break
R = np.dot(hid_r, matrix)
Q = np.linalg.inv(hid_r)
return Q, R
def GaussSeidel(matrix, base):
x = np.array([0] * len(base))
print("x0 = ", x.round(4))
x = np.transpose([x])
D = np.diag(np.diag(matrix))
L = np.tril(matrix)
U = np.triu(matrix) - D
L_i = np.linalg.inv(L)
i = 1
while np.linalg.norm((base - np.dot(matrix, x)), np.inf) > eps:
x = np.dot(L_i, (base - np.dot(U, x)))
print("x{} = ".format(i), x.T.round(4))
i += 1
return x
def func(t): # analytical solution
return (3 - np.power(np.e, -2))/(np.power(np.e, 2) - np.power(np.e, -2)) * np.exp(2 * t) + (np.power(np.e, 2) - 3)/(np.power(np.e, 2) - np.power(np.e, -2)) * np.exp(-2 * t)
def phi(t, cs): # discrete function
if isinstance(t, np.ndarray):
ret = np.array([0] * len(t), dtype=float)
else:
ret = 0
for i in range(len(cs)):
ret += cs[i] * np.power(t, i)
return ret
n_step = 4
t = [(i - 1)/(n_step - 1) for i in range(2, n_step)]
a = np.array([1] + [0] * (n_step - 1), dtype=float)
b = np.array([
[(j - 1) * (j - 2) * np.power(((i - 1)/(n_step - 1)), j - 3) - 4 * np.power(((i - 1)/(n_step - 1)), j - 1) for j in range(1, n_step + 1)] for i in range(2, n_step)
], dtype=float)
c = np.array([1] * n_step, dtype=float)
if n_step > 2:
coefficient_matrix = np.vstack((a, b, c))
else:
coefficient_matrix = np.vstack((a, c))
base = np.transpose([np.array([1] + (n_step - 2) * [0] + [3], dtype=float)])
Q, R = HouseholderQRDecomposition(coefficient_matrix)
R_hat = R[:n_step, :n_step]
d_hat = np.dot(np.transpose(Q), base)[:n_step, :]
cs = GaussSeidel(matrix=R_hat, base=d_hat)
# plot:
plt.rcParams['xtick.direction'] = "in" # ticks direction
plt.rcParams['ytick.direction'] = "in"
axes=plt.gca()
axes.yaxis.set_major_locator(MaxNLocator(5)) # ticks num
axes.xaxis.set_major_locator(MaxNLocator(5))
plt.xticks(fontsize=20, fontweight="bold") # ticks font
plt.yticks(fontsize=20, fontweight="bold")
plt.xlabel("T", fontsize=22, fontweight="bold")
plt.ylabel("Y", fontsize=22, fontweight="bold")
plt.title("Collocation method, n = 4", fontsize=24, fontweight="bold")
plt.plot(np.linspace(0, 1, 100), func(np.linspace(0, 1, 100)), color="red", linewidth=3, label="Analytical solution")
plt.plot(np.linspace(0, 1, 100), phi(np.linspace(0, 1, 100), cs), color="blue", linewidth=3, label="Approximate solution")
# set legend:
legend_font = {
"family": "Times New Roman",
"style" : "normal",
"size" : 20,
'weight': "bold"
}
plt.legend(
bbox_to_anchor=(0.05, 0.95), loc="upper left",
frameon=False, prop=legend_font
)
plt.show()
Galerkin method (finite element analysis)
The choice of splines as basis functions leads to the Finite Element Method as:
y
(
t
)
=
∑
j
=
0
n
+
1
c
j
ϕ
j
(
t
)
y\left( t \right) =\sum_{j=0}^{n+1}{c_j\phi _j(t)}
y(t)=j=0∑n+1cjϕj(t)
ϕ
i
(
t
)
=
{
t
−
t
i
−
1
t
i
−
t
i
−
1
;
w
h
e
n
t
i
−
1
<
t
⩽
t
i
t
i
+
1
−
t
t
i
+
1
−
t
i
;
w
h
e
n
t
i
<
t
⩽
t
i
+
1
0
;
o
t
h
e
r
\phi _i\left( t \right) =\begin{cases} \frac{t-t_{i-1}}{t_i-t_{i-1}}; when\,\,t_{i-1}<t\leqslant t_i\\ \frac{t_{i+1}-t}{t_{i+1}-t_i}; when\,\,t_i<t\leqslant t_{i+1}\\ 0; other\\ \end{cases}
ϕi(t)=⎩
⎨
⎧ti−ti−1t−ti−1;whenti−1<t⩽titi+1−titi+1−t;whenti<t⩽ti+10;other
ϕ
0
(
t
)
=
{
t
1
−
t
t
1
−
t
0
;
w
h
e
n
t
0
<
t
⩽
t
1
0
;
o
t
h
e
r
;
ϕ
n
+
1
(
t
)
=
{
t
−
t
n
t
n
+
1
−
t
n
;
w
h
e
n
t
n
<
t
⩽
t
n
+
1
0
;
o
t
h
e
r
\phi _0\left( t \right) =\begin{cases} \frac{t_1-t}{t_1-t_0}; when\,\,t_0<t\leqslant t_1\\ 0; other\\ \end{cases}; \phi _{n+1}\left( t \right) =\begin{cases} \frac{t-t_n}{t_{n+1}-t_n}; when\,\,t_n<t\leqslant t_{n+1}\\ 0; other\\ \end{cases}
ϕ0(t)={t1−t0t1−t;whent0<t⩽t10;other;ϕn+1(t)={tn+1−tnt−tn;whentn<t⩽tn+10;other
The piecewise-linear “tent” functions
ϕ
i
(
t
)
\phi_{i}(t)
ϕi(t) has the following properties:
ϕ
i
(
j
)
=
δ
i
j
\phi _i\left( j \right) =\delta _{ij}
ϕi(j)=δij
∑
i
=
0
n
+
1
c
i
ϕ
i
(
t
)
=
c
j
\sum_{i=0}^{n+1}{c_i\phi _i\left( t \right)}=c_j
i=0∑n+1ciϕi(t)=cj
∫
a
b
ϕ
i
(
t
)
ϕ
i
+
1
(
t
)
d
t
=
h
6
;
∫
a
b
[
ϕ
i
(
t
)
]
2
d
t
=
2
h
3
\int_a^b{\phi _i\left( t \right) \phi _{i+1}\left( t \right) dt}=\frac{h}{6}; \int_a^b{\left[ \phi _i\left( t \right) \right] ^2dt}=\frac{2h}{3}
∫abϕi(t)ϕi+1(t)dt=6h;∫ab[ϕi(t)]2dt=32h
∫
a
b
ϕ
i
(
t
)
ϕ
i
+
1
′
(
t
)
d
t
=
−
1
h
;
∫
a
b
[
ϕ
i
′
(
t
)
]
2
d
t
=
2
h
\int_a^b{\phi _i\left( t \right) \phi _{i+1}^{\prime}\left( t \right) dt}=-\frac{1}{h}; \int_a^b{\left[ \phi _{i}^{\prime}\left( t \right) \right] ^2dt}=\frac{2}{h}
∫abϕi(t)ϕi+1′(t)dt=−h1;∫ab[ϕi′(t)]2dt=h2
where
h
h
h is the time step.
For BVP:
{
y
′
′
=
f
(
t
,
y
,
y
′
)
y
(
a
)
=
y
a
y
(
b
)
=
y
b
\begin{cases} y^{''}=f\left( t, y, y^{\prime} \right)\\ y\left( a \right) =y_a\\ y\left( b \right) =y_b\\ \end{cases}
⎩
⎨
⎧y′′=f(t,y,y′)y(a)=yay(b)=yb
The Galerkin Method consists of two main ideas. The first is to minimize
r
r
r by
forcing it to be orthogonal to the basis functions:
∫
a
b
y
′
′
(
t
)
ϕ
i
(
t
)
d
t
=
∫
a
b
f
′
(
t
,
y
,
y
′
)
ϕ
i
(
t
)
d
t
\int_a^b{y^{''}\left( t \right) \phi _i\left( t \right) dt}=\int_a^b{f^{\prime}\left( t, y, y^{\prime} \right) \phi _i\left( t \right) dt}
∫aby′′(t)ϕi(t)dt=∫abf′(t,y,y′)ϕi(t)dt
Now apply integration by parts to eliminate the second derivatives:
∫
a
b
y
′
′
(
t
)
ϕ
i
(
t
)
d
t
=
ϕ
i
(
b
)
y
′
(
b
)
−
ϕ
i
(
a
)
y
′
(
a
)
−
∫
a
b
y
′
(
t
)
ϕ
i
′
(
t
)
d
t
\int_a^b{y^{''}\left( t \right) \phi _i\left( t \right) dt}=\phi _i\left( b \right) y^{\prime}\left( b \right) -\phi _i\left( a \right) y^{\prime}\left( a \right) -\int_a^b{y^{\prime}\left( t \right) \phi _{i}^{\prime}\left( t \right) dt}
∫aby′′(t)ϕi(t)dt=ϕi(b)y′(b)−ϕi(a)y′(a)−∫aby′(t)ϕi′(t)dt
According the properties of tent functions:
y
(
a
)
=
∑
i
=
0
n
+
1
c
i
ϕ
i
(
a
)
=
∑
i
=
0
n
+
1
c
i
ϕ
i
(
t
0
)
=
c
0
y\left( a \right) =\sum_{i=0}^{n+1}{c_i\phi _i\left( a \right)}=\sum_{i=0}^{n+1}{c_i\phi _i\left( t_0 \right)}=c_0
y(a)=i=0∑n+1ciϕi(a)=i=0∑n+1ciϕi(t0)=c0
y
(
b
)
=
∑
i
=
0
n
+
1
c
i
ϕ
i
(
b
)
=
∑
i
=
0
n
+
1
c
i
ϕ
i
(
t
n
+
1
)
=
c
n
+
1
y\left( b \right) =\sum_{i=0}^{n+1}{c_i\phi _i\left( b \right)}=\sum_{i=0}^{n+1}{c_i\phi _i\left( t_{n+1} \right)}=c_{n+1}
y(b)=i=0∑n+1ciϕi(b)=i=0∑n+1ciϕi(tn+1)=cn+1
Therefore, the integration formula can be written as (
i
=
1
,
…
,
n
i = 1, \dots, n
i=1,…,n):
∫
a
b
f
(
t
,
y
,
y
′
)
ϕ
i
(
t
)
d
t
+
∫
a
b
y
′
(
t
)
ϕ
i
′
(
t
)
d
t
=
0
\int_a^b{f\left( t, y, y^{\prime} \right) \phi _i\left( t \right) dt}+\int_a^b{y^{\prime}\left( t \right) \phi _{i}^{\prime}\left( t \right) dt}=0
∫abf(t,y,y′)ϕi(t)dt+∫aby′(t)ϕi′(t)dt=0
Application
For BVP:
{
y
′
′
=
4
y
y
(
0
)
=
1
y
(
1
)
=
3
\begin{cases} y^{''}=4y\\ y\left( 0 \right) =1\\ y\left( 1 \right) =3\\ \end{cases}
⎩
⎨
⎧y′′=4yy(0)=1y(1)=3
Through collocation method:
∑
j
=
0
n
+
1
c
j
[
4
∫
0
1
ϕ
i
(
t
)
ϕ
j
(
t
)
d
t
+
∫
0
1
ϕ
j
′
(
t
)
ϕ
i
′
(
t
)
d
t
]
\sum_{j=0}^{n+1}{c_j\left[ 4\int_0^1{\phi _i\left( t \right) \phi _j\left( t \right) dt}+\int_0^1{\phi _{j}^{\prime}\left( t \right) \phi _{i}^{\prime}\left( t \right) dt} \right]}
j=0∑n+1cj[4∫01ϕi(t)ϕj(t)dt+∫01ϕj′(t)ϕi′(t)dt]
System of linear equations can be obtained:
(
α
β
0
⋯
⋯
⋯
0
β
α
β
0
⋯
⋯
0
0
β
α
β
0
⋯
0
0
0
⋱
⋱
⋱
0
0
⋮
⋮
0
β
α
β
0
⋮
⋮
⋮
0
β
α
β
0
0
0
0
0
β
α
)
(
c
1
⋮
⋮
⋮
⋮
⋮
c
n
)
=
(
−
y
a
β
0
⋮
⋮
⋮
0
−
y
b
β
)
\left( \begin{matrix} \alpha& \beta& 0& \cdots& \cdots& \cdots& 0\\ \beta& \alpha& \beta& 0& \cdots& \cdots& 0\\ 0& \beta& \alpha& \beta& 0& \cdots& 0\\ 0& 0& \ddots& \ddots& \ddots& 0& 0\\ \vdots& \vdots& 0& \beta& \alpha& \beta& 0\\ \vdots& \vdots& \vdots& 0& \beta& \alpha& \beta\\ 0& 0& 0& 0& 0& \beta& \alpha\\ \end{matrix} \right) \left( \begin{array}{c} c_1\\ \vdots\\ \vdots\\ \vdots\\ \vdots\\ \vdots\\ c_n\\ \end{array} \right) =\left( \begin{array}{c} -y_a\beta\\ 0\\ \vdots\\ \vdots\\ \vdots\\ 0\\ -y_b\beta\\ \end{array} \right)
αβ00⋮⋮0βαβ0⋮⋮00βα⋱0⋮0⋯0β⋱β00⋯⋯0⋱αβ0⋯⋯⋯0βαβ00000βα
c1⋮⋮⋮⋮⋮cn
=
−yaβ0⋮⋮⋮0−ybβ
where
α
=
8
3
h
+
2
h
;
β
=
2
3
h
−
1
h
;
y
a
=
1
;
y
b
=
3
\alpha =\frac{8}{3}h+\frac{2}{h}; \beta =\frac{2}{3}h-\frac{1}{h}; y_a=1; y_b=3
α=38h+h2;β=32h−h1;ya=1;yb=3
def func(t): # analytical solution
return (3 - np.power(np.e, -2))/(np.power(np.e, 2) - np.power(np.e, -2)) * np.exp(2 * t) + (np.power(np.e, 2) - 3)/(np.power(np.e, 2) - np.power(np.e, -2)) * np.exp(-2 * t)
n_step = 10
step_h = (1.0 - 0.0)/(n_step + 1)
b_value1, b_value2 = 1.0, 3.0 # boundary value: y(0) = 1.0, y(1) = 3.0
alpha = 8/3 * step_h + 2/step_h
beta = 2/3 * step_h - 1/step_h
a = np.array([alpha, beta] + [0] * (n_step - 2), dtype=float)
b = np.array([
([0] * (i - 1) + [beta, alpha, beta] + [0] * (n_step - 3 - (i - 1))) for i in range(1, n_step - 1)
])
c = np.array([0] * (n_step - 2) + [beta, alpha], dtype=float)
if n_step > 2:
coefficient_matrix = np.vstack((a, b, c))
else:
coefficient_matrix = np.vstack((a, c))
base = np.transpose([np.array([-b_value1 * beta] + [0] * (n_step - 2) + [-b_value2 * beta], dtype=float)])
omega = np.hstack((np.array([b_value1]), np.transpose(GaussSeidel(matrix=coefficient_matrix, base=base)).squeeze(), np.array([b_value2])))
# plot:
plt.rcParams['xtick.direction'] = "in" # ticks direction
plt.rcParams['ytick.direction'] = "in"
axes=plt.gca()
axes.yaxis.set_major_locator(MaxNLocator(5)) # ticks num
axes.xaxis.set_major_locator(MaxNLocator(5))
plt.xticks(fontsize=20, fontweight="bold") # ticks font
plt.yticks(fontsize=20, fontweight="bold")
plt.xlabel("T", fontsize=22, fontweight="bold")
plt.ylabel("Y", fontsize=22, fontweight="bold")
plt.title("Galerkin method, n = 10", fontsize=24, fontweight="bold")
plt.scatter(np.linspace(0, 1, 12), func(np.linspace(0, 1, 12)), color="red", marker='o', s=100, label="Y")
plt.plot(np.linspace(0, 1, 100), func(np.linspace(0, 1, 100)), color="red", linewidth=3)
plt.scatter(np.linspace(0, 1, 12), omega, color="blue", marker='*', s=150, label="Omega")
# set legend:
legend_font = {
"family": "Times New Roman",
"style" : "normal",
"size" : 20,
'weight': "bold"
}
plt.legend(
bbox_to_anchor=(0.95, 0), loc="lower right",
frameon=False, prop=legend_font
)
plt.show()