数值分析的上机实验难度为 \(10\) 的话,纸笔题的难度恐怕就是碘化银的 \(K_{sp}\)(大致是 \(4.2×10^{−51}\)(
我不会 MATLAB,又懒得学,就选了 Python。反正作业就是造轮子,也不能用 MATLAB 的函数,那 Python 不亏。
声明:本文更新时间均在数值实验作业截止日期后,详情可查询 commits 记录,不存在任何抄袭或协助抄袭现象。
线性方程组直接解法
一开始还是有点虚的,NumPy 和 Pandas 都是前学后忘,于是第一次作业拖到周末才写,意外地顺利。
反正就是不停地写循环,略烦。
\(LDL^T\) 法
算法就是 \(LDL^T\) 法。书上啥都有。
比较痛的一点是,这个方法为了提高效率把 \(L\) 和 \(D\) 直接存储在 \(A\) 里了,但是最后计算误差的时候需要调用原值 \(A\),所以做分解之前应该先备份一个原矩阵。向量 \(b\) 的备份同理。
实现如下:
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# 任务一:改进的平方根法求解线性方程组
import numpy as np
from numpy.linalg import norm
n = 32
H = 1./(np.arange(1, n + 1)+np.arange(0, n)[:,np.newaxis]) #构造n阶Hilbert矩阵
xtrue = np.ones((n, 1))
x = np.ones((n, 1))
b = H.dot(xtrue)
#待定系数法计算LDL^t分解
K = H.copy()
c = b.copy()
for j in range(0, n):
for k in range(0, j):
K[j, j] = K[j, j] - K[j, k] * K[j, k] * K[k, k]
for i in range(j + 1, n):
for k in range(0, j):
K[i, j] = K[i, j] - K[i, k] * K[j, k] * K[k, k]
K[i,j] = K[i, j] / K[j, j]
#写出L和D的具体形式
L = np.zeros((n, n))
D = np.zeros((n, n))
for i in range(0, n):
L[i, i] = 1
for j in range(0, i):
L[i, j] = K[i, j]
D[i, i] = K[i, i]
#解方程Ly=b
y = np.zeros((n, 1))
y[0] = c[0]
for i in range(1, n):
for j in range(0, i):
c[i] = c[i] - K[i, j] * y[j]
y[i] = c[i]
#解方程DL^tx=y
x = np.zeros((n, 1))
x[n-1] = c[n-1] / K[n-1,n-1]
for i in range(n-2, -1, -1):
x[i] = y[i] / K[i,i]
for j in range(i+1, n):
x[i] = x[i] - K[j,i] * x[j]
#输出结果
print('相对残量为: %.4f'%(norm(b - np.matmul(H, x)) / norm(b)))
print('相对误差为: %.4f'%(norm(x - xtrue) / norm(xtrue)))输出结果:
1
2相对残量为: 0.0000
相对误差为: 111.3149残量为 \(0\) 说明分解正确。考虑到 \(Hilbert\) 矩阵是个病态矩阵,这个值还算可以接受。虽然真值是一个分量全为 \(1\) 的 \(32\) 阶向量,而数值解从第 \(7\) 个分量开始就大量出现绝对值是两位数的情况了。
\(LDL^T\) 分解从结果来看仍然是 \(LU\) 分解的一种变形,但在本实验中,利用 \(LU\) 分解得到的数值解各分量量级偏小,甚至达到 \(10^{-5}\) 级别。输出结果为:
1
2相对残量为: 0.0000
相对误差为: 73385.0585相对误差大了两个量级,说明对于对称矩阵来说 \(LDL^T\) 分解是更好的选择。
我在做数值实验的过程中先解决了 \(LU\) 分解,回头又做了 \(LDL^T\) 分解,因此二者的结果不能很好地匹配也造成了不小的困难..
\(LU\) 法
我感觉最难的是从
.mat
文件里载入矩阵...\(LU\) 法,学过线代就能做,我大一上学期的 C++ 大作业里还写这个了..
实现如下:
1 | # 任务二:实现按列存储的JKI型LU分解 |
类似地,注意算法虽然是在 \(A\)
上进行的,但在程序中需要保留 \(A\)
的原值来计算相对误差,因此在函数中使用的应该是 \(A\) 的复制,也就有了
A=B.copy()
这一步。
输出结果:
1 | relerr = 0.0000 |
相对误差接近于 \(0\),说明实验是成功的。
后记
不是,这玩意不是我后来咕咕了没再写,而是布置过一次作业之后就再也没有了(
本来说是基本每一讲后面都有数值实验的,结果不知道是老师咕咕还是助教咕咕,总之是并没有再布置过实验,少了很多乐趣。之前看过贵系数值分析的资料 Repo,两边的风格还是很不一样的。
就这么草率地结束了..