摘要:在本报告中介绍了一套图形界面的矩阵计算器的的想法与需求分析、设计、实现,测试和改进。在想法与需求分析中介绍了我研发此程序的契机和原因。在设计中介绍了对该程序的设计,以及如何实现。实现中介绍了对关键代码的分析和解释。测试以图片的形式证明了一些具体功能的实现和验证。最后的改进中介绍了程序的还存在的漏洞与需要改进的地方。 |
关键字:矩阵;计算;图形化;方便 |
- 想法与需求分析
- 想法
作为一名工科专业大学生,我们会在读大学的途中遇到《线性代数》这样一门课程。这门课程主要讲了与矩阵相关的计算与分析。但是在学习过程中,我们会遇到一些比较复杂的矩阵的运算,如求矩阵的逆,求矩阵的行列式的值,求矩阵的特征值,等。对于刚刚接触这门课程的大学生来说十分的困难,有时候疯狂的计算了很长时间都不能得到正确的结果,让人的心态十分的炸裂,很容易让我们脆弱的心灵受到伤害,于是我设计了这款矩阵计算器。
-
- 需求分析
当前比较常见的计算器,对矩阵的计算操作十分的复杂,首先要定义矩阵,然后再定义的矩阵中选取几个进行运算,矩阵最大只有4*4,最多也只能存储4个,功能也不全面。而且还要仔细的阅读说明书才能正确的操作,十分的复杂。手机上自带的计算器也没有计算矩阵的功能。而更高级的计算器又需要花费很多的钱。而我利用python编写了一款带有图形界面的矩阵计算器,可以实现对于矩阵的一些基本的运算与操作。对于大学生学习《线性代数》有很大的帮助,在它的帮助下学习效率会得到提高。不仅提高了计算的效率,同时也能验证手算结果是否正确。
- 设计
- 布局
首先对计算器的整体构造进行排版和布局,仿照传统计算器的样式,利用pyqt5,先在界面的上半部分设计一个多行的可编辑文本框plainTextEdit。之后在下方利用layout进行排版创建一个5*5的按钮组,按钮组中的25个按钮分别为:空格,行列式的值,转置矩阵,特征值与向量,清空,删除,逆矩阵,0,高斯消元法。换行,7,8,9,+,-,4,5,6,X,.,1,2,3,Ans,=。通过按钮点击相应事件用来实现计算器不同的功能。效果如图:
-
- 功能
本矩阵计算器通过点击按钮实现矩阵的输入,加减乘计算,逆矩阵,转置矩阵,求特征值与特征向量,求行列式的值,通过增广矩阵的高斯消元法求一些方程的解。
点击数字或者符号,会直接在多行文本编辑框内出现相应的数字或者符号。输入每个数据需要用空格隔开,点击换行会输出下一行的数据。
当输入一个矩阵后,若点击逆矩阵,转置矩阵等操作时,在输入矩阵满足前提条件情况下会直接将结果输出在多行文本编辑框内,并将结果存入Ans中。若点击等于号,则会把该矩阵存入Ans中。若点击运算符号则会自动换行,输入下一个矩阵。当第二个矩阵输入完成后,点击等于号,在输入矩阵满足前提条件情况下会直接将结果输出在多行文本编辑框内,并将结果存入Ans中。
点击Ans,后点击等于号,会将Ans存储的结果输出。而且上述对于矩阵的操作对Ans也同样生效,对于矩阵的运算也可以结合Ans进行。
- 实现
- 点击数字按钮
# 点击1 def clickbutton1(self=None): global bo if bo == 0: self.plainTextEdit.insertPlainText("1") else: bo = 0 self.plainTextEdit.appendPlainText("1")
分析:
以数字1为例。bo为全局变量,主要用来判断当前是否处于换行状态,处于换行状态使用函数iappendPlainText会使数字输出在新的一段,若不处于换行状态则使用insertPlainText函数直接在本段追加。
-
- 点击符号按钮
- def clickbuttonadd(self=None): self.plainTextEdit.appendPlainText("+") global bo bo = 1
分析:
以加号为例。对运算更加方便的操作,点击符号按钮后直接另起一段进行输入,并将换行标志bo置为1,下一次的输入也是另起一段,方便矩阵的输入。
3.3点击操作按钮
# 点击清空 def clickbuttonclear(self=None): self.plainTextEdit.clear() # 点击换行 def clickbutton_huanhang(self=None): global bo bo = 1 # 点击删除 def clickbuttondelete(self=None): text_cursor = QTextCursor(self.plainTextEdit.document()) text_cursor.movePosition(QTextCursor.End) text_cursor.deletePreviousChar()
分析:
点击清空会用clear函数将文本框清空,点击换行会让换行标志bo为1,使下一次输入另起一段,而点击删除按钮则是使用QTextCursor选择文本编辑框中的内容并将光标移动到最后一个字符处,使用deletPreviousChar函数直接将前一个字符删除。
3.4点击逆矩阵按钮
def clickbuttoninverse(self=None): st = self.plainTextEdit.toPlainText() x = len(st.split('\n')) y = len((st.split('\n')[0]).split()) global ans if x != y: self.plainTextEdit.setPlainText("错误") else: if st == "Ans": num = ans else: num = np.ones((x, y)) for i in range(x): for j in range(y): num[i][j] = (st.split('\n')[i]).split()[j] p = np.linalg.det(num) if p == 0: self.plainTextEdit.setPlainText("错误") num = np.linalg.inv(num) ans = num self.plainTextEdit.clear() for i in range(x): for j in range(y): if j == 0: self.plainTextEdit.appendPlainText(str(num[i][j])) self.plainTextEdit.insertPlainText(" ") else: self.plainTextEdit.insertPlainText(str(num[i][j])) self.plainTextEdit.insertPlainText(" ")
分析:
首先点击该按钮时,文本编辑框内已经有了一个完整的矩阵或是Ans。之后将文本框内的数据读取为字符串。之后通过split()对字符串进行操作,先以换行符进行分割,可以求出当前矩阵的行数,之后选取一行以空格进行分割,可以求出当前矩阵的列数。之后通过求得的行数与列数创建一个全1矩阵num。矩阵num的每个元素与字符串中的位置一一对应。通过分割字符串。循环赋值构建矩阵,并求其行列式,因为行列式为0的矩阵没有逆矩阵因此输出错误。若行列式不为0,利用np.linalg.inv函数计算逆矩阵。按矩阵顺序输出结果到文本编辑框内,并将结果存入Ans中。
若文本编辑框内本来便是Ans,则直接进行运算,省去提取矩阵的步骤。
3.5点击行列式的值按钮
# 点击行列式的值 def clickbuttondeterminant(self=None): st = self.plainTextEdit.toPlainText() x = len(st.split('\n')) y = len((st.split('\n')[0]).split()) global ans if x != y: self.plainTextEdit.setPlainText("错误") else: if st == "Ans": num = ans else: num = np.ones((x, y)) for i in range(x): for j in range(y): num[i][j] = (st.split('\n')[i]).split()[j] p = np.linalg.det(num) p = round(p, 7) ans = p self.plainTextEdit.setPlainText(str(p))
分析:
点击该按钮时,会有与逆矩阵相似的方法提取当前文本编辑框中的矩阵,之后利用np.linalg.det函数 计算行列式的值,因为矩阵转化之后所有数据均为浮点数,而浮点数会在计算的过程中产生不可避免的精度误差,于是我采用了保留小数的方法消除误差,并将结果存入Ans中。
3.6点击转置矩阵按钮
# 点击转置矩阵 def clickbuttontransposition(self=None): st = self.plainTextEdit.toPlainText() x = len(st.split('\n')) y = len((st.split('\n')[0]).split()) global ans if st == "Ans": num = ans else: num = np.ones((x, y)) for i in range(x): for j in range(y): num[i][j] = (st.split('\n')[i]).split()[j] num = num.transpose() ans = num self.plainTextEdit.clear() self.plainTextEdit.setPlainText(str(num))
分析:
点击转置矩阵的按钮后,会用类似逆矩阵的方法求得当前矩阵,利用.transpose函数求得转置矩阵并输出,结果保留在Ans中。
3.7点击特征值与特征向量按钮
# 点击特征值与特征向量 def clickbuttoneigenvalues(self=None): st = self.plainTextEdit.toPlainText() x = len(st.split('\n')) y = len((st.split('\n')[0]).split()) if x != y: self.plainTextEdit.setPlainText("错误") else: global ans if st == "Ans": num = ans else: num = np.ones((x, y)) for i in range(x): for j in range(y): num[i][j] = (st.split('\n')[i]).split()[j] eigenvalues, eigenvectors = np.linalg.eig(num) self.plainTextEdit.clear() self.plainTextEdit.appendPlainText(str(eigenvalues)) self.plainTextEdit.appendPlainText(str(eigenvectors))
分析:
由于设计时出现了26个按钮,无法漂亮的排版,于是只能将求特征值与特征向量合并到一起,只是无法将两个结果赋值到Ans中。
点击特征值与特征向量的按钮后,会用类似逆矩阵的方法求得当前矩阵,利用np.linalg.eig函数求得转置矩阵并输出。
3.8点击高斯消元法按钮
def clickbuttongaosi(self=None): st = self.plainTextEdit.toPlainText() x = len(st.split('\n')) y = len((st.split('\n')[0]).split()) num = np.ones((x, y)) res = np.ones((x, y - 1)) for i in range(x): for j in range(y): num[i][j] = (st.split('\n')[i]).split()[j] for i in range(x): for j in range(y - 1): res[i][j] = num[i][j] if np.linalg.matrix_rank(res) < np.linalg.matrix_rank(num): self.plainTextEdit.setPlainText("有无数解") if np.linalg.matrix_rank(res) > np.linalg.matrix_rank(num): self.plainTextEdit.setPlainText("无解") if np.linalg.matrix_rank(res) == np.linalg.matrix_rank(num): self.plainTextEdit.clear() for i in range(x - 1): for j in range(i + 1, x): temp = num[j][i] / num[i][i] for t in range(i, y): num[j][t] = num[j][t] - temp * num[i][t] for i in range(np.linalg.matrix_rank(num) - 1, -1, -1): res[i][1] = num[i][y - 1] for j in range(i + 1, np.linalg.matrix_rank(num)): res[i][1] = res[i][1] - num[i][j] * res[j][0] res[i][0] = res[i][1] / num[i][i] for i in range(np.linalg.matrix_rank(num)): res[i][0] = round(res[i][0], 7) self.plainTextEdit.appendPlainText(str(res[i][0]))
分析:
首先使用了类似于求逆矩阵中将字符串转化为矩阵的方法。得到矩阵num。之后删去矩阵的最后一列,得到了系数矩阵res。利用np.linalg.matrix_rank函数分别求出系数矩阵与增广矩阵的秩。
当方程组的系数矩阵的秩与方程组增广矩阵的秩相等且均等于方程组中未知数个数n的时候,方程组有唯一解。
当方程组的系数矩阵的秩与方程组增广矩阵的秩相等且均小于方程组中未知数个数n的时候,方程组有无穷多解。
当方程组的系数矩阵的秩小于方程组增广矩阵的秩的时候,方程组无解。
当确定方程组有唯一解后,利用循环,将每一行的第一个非0的数转化为0,得到一个上三角矩阵。之后利用系数矩阵的第一列存储方程的解,第二列存储计算过程中的中间值,倒序求取方程的解并将结果输出。
3.9点击等号按钮
# 点击等于号 def clickbuttequal(self=None): st = self.plainTextEdit.toPlainText() global ans temp = 1 x = len(st.split('\n')) if st == "Ans": self.plainTextEdit.setPlainText(str(ans)) else: for i in range(x): if st.split('\n')[i] == "Ans" and i == x - 1: temp = 0 x1 = x - 2 y1 = len((st.split('\n')[0]).split()) op = st.split('\n')[x - 2] num2 = ans x2 = ans.shape[0] y2 = ans.shape[1] num1 = np.ones((x1, y1)) for t in range(x1): for j in range(y1): num1[t][j] = (st.split('\n')[t]).split()[j] break if st.split('\n')[i] == "Ans" and i == 0: temp = 0 x2 = x - 2 y2 = len((st.split('\n')[x2]).split()) num1 = ans op = st.split('\n')[1] x1 = ans.shape[0] y1 = ans.shape[1] num2 = np.ones((x2, y2)) for t in range(2, x): for j in range(y2): num2[t -2][j] = (st.split('\n')[t]).split()[j] break if temp: x1 = 0 for i in range(x): if (st.split('\n')[i]).split()[0] == 'X' or (st.split('\n')[i]).split()[0] == '+' or \ (st.split('\n')[i]).split()[0] == '-': op = (st.split('\n')[i]).split()[0] x1 = i break if x1 == 0: y = len((st.split('\n')[0]).split()) num = np.ones((x, y)) for i in range(x): for j in range(y): num[i][j] = (st.split('\n')[i]).split()[j] ans = num self.plainTextEdit.setPlainText(str(ans)) else: y1 = len((st.split('\n')[0]).split()) y2 = len((st.split('\n')[x1 + 1]).split()) x2 = x - x1 - 1 print(x1, y1, x2, y2) num1 = np.ones((x1, y1)) num2 = np.ones((x2, y2)) for i in range(x1): for j in range(y1): num1[i][j] = (st.split('\n')[i]).split()[j] for i in range(x1 + 1, x): for j in range(y2): num2[i - 1 - x1][j] = (st.split('\n')[i]).split()[j] if x1 != 0: if op == '+': if x1 == x2 and y1 == y2: ans = num1 + num2 self.plainTextEdit.setPlainText(str(ans)) else: self.plainTextEdit.setPlainText("错误") if op == '-': if x1 == x2 and y1 == y2: ans = num1 - num2 self.plainTextEdit.setPlainText(str(ans)) else: self.plainTextEdit.setPlainText("错误") if op == 'X': if y1 == x2: ans = np.dot(num1, num2) self.plainTextEdit.setPlainText(str(ans)) else: self.plainTextEdit.setPlainText("错误")
分析:
点击等于号后需要分情况讨论。
当点击等于号后检测到文本输入框内只有一个矩阵时,将矩阵存入Ans中并输出。
当点击等于号后检测到文本输入框内只有Ans时,直接将Ans的内容输出。
当点击等于号后检测到文本输入框内有两个矩阵和运算符时,首先将文本框中的内容全部转化为字符串,以换行符为分割符进行分割,求出当前总行数。
之后进行循环,检测每一行是否为Ans,若存在Ans,记录其出现的位置。
若出现在第一行。则将其赋值给运算矩阵num1,记录其行数和列数,运算符便位于第二行,将运算符记录。然后使用经典方法将运算符之后字符串的分割赋值转化为计算矩阵num2。
若出现在最后一行。则将其赋值给运算矩阵num2,记录其行数和列数,运算符便位于倒数二行,将运算符记录。然后使用经典方法将运算符之后字符串的分割赋值转化为计算矩阵num1。
若不存在Ans,则需要寻找运算符的位置找到之后,记录运算符。将运算符之前的字符串使用经典方法分割赋值给运算矩阵num1,将运算符之后的字符串使用经典方法分割赋值给运算矩阵num2。
之后进入计算的判断,首先判断运算符。
若为加或减,则判断两个运算矩阵行和列是否相等,不满足输出错误,满足进行运算,并把结果存入Ans中。
若为乘,则判断第一个矩阵列是否等于第二个矩阵的行,不满足输出错误,满足进行运算,并把结果存入Ans中。
4.测试
4.1逆矩阵
样例输入:
(1)1 2
2 1
(2)1 1 1
2 3 4
4 5 6
4.2转置矩阵
样例输入:
-
- 2
3 4
(2)1 2
4 5
8 9
样例输出:
4.3行列式的值
样例输入:
(1)1 2
2 1
(2)1.2 2 1
4.5 4 3
8.2 1 2
样例输出:
4.4特征值与特征向量
样例输入:
(1)1 2
2 1
样例输出:
4.5等号计算
样例输入:
(1)1 2
2 1
+
4 4
7 8
(2)Ans(此时Ans为上一次(1)运算的结果)
-
2 7
1 1
(3)1 2 3
4 5 6
X
Ans(此时Ans为上一次(2)运算的结果)
样例输出:
- 改进
我所编辑的矩阵计算器能基本的解决一些基础的有关的矩阵的简答基本的计算,供学生使用,系统的应用了在Python课堂上学习的pyqt5可视化界面的编辑。对字符串的处理以及分析,对条件语句的熟练使用等等。但是还是有许多的弊端,如对于运算错误的处理,因为在编辑计算器的过程中我只是将我所能想到的计算失败的情况做出了处理,并不能涵盖所有的情况,如使用者输入了一个不标准的矩阵,使用者无法进行2个以上的矩阵的运算。需要后续更系统的将所有的不能正常运算的情况总结起来,专门设立对错检验函数,应该会让bug变得更少。
在让其更严谨之后还可以拓展按钮,进行功能的拓展,如矩阵的对角化,求矩阵的正定矩阵等。或是将其与其他相关科目结合,如可以自动计算《数值计算方法》这门课中解多元方程组的迭代矩阵,并判断其是否收敛。等等。甚至可以将矩阵图形化,根据对应的矩阵中的值汇出相应的函数图像。
- 致谢
首先要感谢我的老师,在我没有接触过python的情况下带我由浅入胜,由表及里逐步的学习python,逐步的拓展了我的眼界,提高了我的思维。到最后结课的时候也能独自的完成一个比较复杂的大型程序来实现自己的一些想法。虽然过程比较漫长和困难但给了我极大的收获与进步文章来源:https://www.toymoban.com/news/detail-716186.html
同时网络上的一些对于python库的归纳总结让我的研究更加省时省力。文章来源地址https://www.toymoban.com/news/detail-716186.html
到了这里,关于矩阵计算器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!